ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

【Java】Lambda表达式

2022-07-07 12:04:31  阅读:99  来源: 互联网

标签:Java String int void public 表达式 Lambda


概述

之前在自学的时候接触过 Lambda 表达式,但那会其实用的并不多,对于其也是大概有个了解,但在阅读公司代码的时候,发现在对于集合的处理时都是转成stream流再通过foreach结合 Lambda 表达式进行处理的,所以打算再重新学学这部分的内容

先来看看网上对于它的定义

Lambda 表达式是 Java 8 的重要更新,它支持将代码块作为方法参数、允许使用更简洁的代码来创建只有一个抽象方法的接口的实例。 Lambda 表达式的主要作用就是可以用于简化创建匿名内部类对象,Lambda 表达式的代码块将会用于实现抽象方法的方法体,Lambda 表达式就相当于一个匿名方法

结合上面的描述可以得到一个结论,Lambda 表达式在匿名函数以及集合的 stream 操作有着重要的作用

先来看一段简单的代码

public class TestLambda {
       public static void main(String[] args) {
       Thread thread = new Thread(new MyRunnable());
       thread.start();
       thread.close();
    }
}
class MyRunnable implements Runnable{
	@Override
        public void run() {
            System.out.println("Hello");
        }
}

现在将代码简化一下,使用匿名内部类来实现 Runnable 接口

public class TestLambda {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello");
            }
        });
        thread.start();
    }
}

然而上面这段代码还不是最简单的,再看下面这个

public class TestLambda {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("Hello")).start();
    }
}

这样对比下来,发现使用 Lambda 表达式确实能极大的让代码更简洁

语法

Lambda 表达式由三部分构成

  • 形参列表:形参列表允许省略类型,如果形参列表中只有一个参数,形参列表的圆括号也可以省略
  • 箭头(->)
  • 代码块:可以是表达式也可以代码块,是函数式接口里方法的实现。代码块可返回一个值或者什么都不反回,这里的代码块块等同于方法的方法体。如果是表达式,也可以返回一个值或者什么都不返回

总结一下就是:实现的这个接口中的抽象方法中的形参列表 -> 抽象方法的处理

无返回值有形参的方法

public interface MyInterface {
    public abstract void show(int a,int b);
}
public class MyTest {
    public static void main(String[] args) {
        MyInterface myInterface = new MyInterface() {
            @Override
            public void show(int a, int b) {
                System.out.println(a + b);
            }
        };

        //简写1:方法名可以自己推断出来
        MyInterface myInterface1 = (int a, int b) -> {
            System.out.println(a + b);
        };

        //简写2:可以省略形参列表中的形参类型
        MyInterface myInterface2 = (a, b) -> {
            System.out.println(a + b);
        };

        //简写3:如果抽象方法中只有一行代码,可以省略方法体的大括号,当然,如果不止一行,就不能省略
        MyInterface myInterface3 = (a, b) -> System.out.println(a + b);
    }
}

有返回值的抽象方法

public interface MyInterface {
    public abstract int test(int a,int b);
}
public class MyTest {
    public static void main(String[] args) {
        MyInterface test1 = new MyInterface() {
            @Override
            public int test(int a, int b) {
                return a - b;
            }

        //简写1:
        MyInterface test3 = (a, b) -> {return a - b;};

        //简写3:这个有返回值的方法,不能直接去掉大括号,还需要去掉return关键字
        MyInterface test4 = (a, b) -> a - b;
    }
}

只有一个形参的抽象方法

public interface MyInterface {
    public abstract int show(int a);
}
public class MyTest {
    public static void main(String[] args) {
        //形参列表中只有一个参数,可以去掉形参的括号
        MyInterface myInterface = a -> a-20;
    }
}

Lambda表达式作为参数

public class Main {
  public static void main(String[] argv) {
    engine((x,y)-> x + y);
    engine((x,y)-> x * y);
    engine((x,y)-> x / y);
    engine((x,y)-> x % y);
  }
  private static void engine(Calculator calculator){
    int x = 2, y = 4;
    int result = calculator.calculate(x,y);
    System.out.println(result);
  }
}

@FunctionalInterface
interface Calculator{
  int calculate(int x, int y);
}

Lambda 表达式与函数式接口

在以往的资料上,他们大多爱说这么一句话

Lambda 表达式的类型,也被称为目标类型(target type)。Lambda 表达式的目标类型必须是函数式接口(functional interface)

我相信很多想要了解Lambda表达式的人一查资料,发现这么一句话,肯定是一头雾水的。暂且先不讨论这句话的含义,但有一点是可以达成共识的,那就是:Lambda表达式和函数接口一定存在着千丝万缕的关联

事实上确实是这样的,函数式接口和Lambda表达式同为Java8的新特性,它大致可以用以下的文字来形容

函数式接口就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。可以通过 Lambda 表达式来创建该接口的对象

通过查询 Java 8 的 API 文档,可以发现大量的函数式接口,例如熟知的 Runnable 接口就是一个函数式接口(其中只有一个抽象的run()方法)。Java 8 还提供了@FunctionalInterface注解,该注解用于告诉编译器校验接口必须是函数式接口,否则就报错

由于 Lambda 表达式的结果就是被当做对象/实例,因此,可以使用 Lambda 表达式进行赋值

但是下面这个是个错误的实例

Object obj = () -> {
    for (int i = 0; i < 100; i++) {
        System.out.println(i);
    }
};

其实如果已经了解了 Lambda 表达式与函数式接口的关系,大概都不会犯这个错,但考虑到之前有提到过“Lambda 表达式的结果就是被当成对象/实例的”,所以还是把它拿出来说说。这个程序执行之后会报Target type of a lambda conversion must be an interface,将 Lambda 表达式赋值给 Object 类型的变量,编译器只能推断出它的表达类型为 Object,而 Object 并不是函数式接口,因此就报错了

为了保证 Lambda 表达式的目标类型是明确的函数式接口,有如下三种常见方式:

  • 将 Lambda 表达式赋值给函数式接口类型的变量
  • 将 Lambda 表达式作为函数式接口类型的参数传给某个方法
  • 使用函数式接口对 Lambda 表达式进行强制类型转换

那么以上的错误就可以更改成

//这里就使用函数式接口 Runnable 对 Lambda 表达式进行了强制转换
Object obj = (Runnable)() -> {
    for (int i = 0; i < 100; i++) {
        System.out.println(i);
    }
};

奇奇怪怪的运算符——“::”

一个简单问题的思考

在使用 Lambda 表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿什么参数做什么操作。那么考虑一种情况:如果我们在 Lambda 中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑?

来看例子:冗余的 Lambda 场景

@FunctionalInterface
public interface Printable {
    /**
     * 接收一个字符串参数,打印显示它
     * @param str 字符串
     */
    public abstract void print(String str);
}
-------------------------------------------------------
public class Demo01 {
 
    public static void main(String[] args) {
        printString(s -> System.out.println(s));
    }
 
    private static void printString(Printable printable) {
        printable.print("Hello, World!");
    }
 
}

这段代码的问题在于,对字符串进行控制台打印输出的操作方案,明明已经有了现成的实现,那就是 System.out 对象中的println(String)方法。既然 Lambda 希望做的事情就是调用println(String)方法,那何必自己手动调用呢?

解决方式

public class Demo02 {
 
    public static void main(String[] args) {
        printString(System.out::println);
    }
    
    private static void printString(Printable printable) {
        printable.print("Hello, World!");
    }
 
}

方法引用

双冒号::为引用运算符,而它所在的表达式被称为方法引用。如果 Lambda 要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为 Lambda 的替代者

例如上例中,System.out对象中有一个重载的println(String)方法恰好就是我们所需要的。那么对于printString方法的函数式接口参数,对比下面两种写法,完全等效

// Lambda表达式写法
s -> System.out.println(s);
// 方法引用写法
System.out::println

Lambda 中传递的参数,一定是方法引用中的那个方法可以接收的类型,否则会出现编译错误。上例中 println 方法可以接收 String 类型的参数,所以才有两种等价的写法

通过对象名引用成员方法

假如我们已经在一个类中定义了一个方法的具体实现

public class MethodRefObject {
 
    public void printUpperCase(String str) {
        System.out.println(str.toUpperCase());
    }
 
}

然后函数式接口定义如下

@FunctionalInterface
public interface Printable {
    public abstract void print(String str);
}

拿到参数之后经 Lambda 之手,继而传递给toUpperCase()方法去处理

public class Demo {
 
    public static void main(String[] args) {
        printString(s -> s.toUpperCase());
    }
 
    private static void printString(Printable lambda) {
        lambda.print("Hello");
    }
 
}

折腾了一大圈发现,其实已经有方法实现了 Lambda 表达式想要实现的功能

这个时候,当需要使用这个 printUpperCase 成员方法来替代 Printable 接口的 Lambda 的时候,已经具有了 MethodRefObject 类的对象实例,则可以通过对象名引用成员方法

public class Demo {
    
    public static void main(String[] args) {
        MethodRefObject obj = new MethodRefObject();
        printString(obj::printUpperCase);
    }
    
    private static void printString(Printable lambda) {
        lambda.print("Hello");
    }
    
}

通过类名引用静态方法

Math 类中已经存在了静态方法abs(),现在需要通过 Lambda 去调用这个方法,有两种表示方式

第一种,使用函数式接口

@FunctionalInterface
public interface CalculationAble {
    int calculation(int num);
}
public class Demo {
 
    public static void main(String[] args) {
        method(-666, n -> Math.abs(n));
    }
 
    private static void method(int num, CalculationAble lambda) {
        System.out.println(lambda.calculation(num));
    }
    
}

第二种,可以使用类名来引用静态方法

public class Demo {
 
    public static void main(String[] args) {
        method(-666, Math::abs);
    }
 
    private static void method(int num, CalculationAble reference) {
        System.out.println(reference.calculation(num));
    }
 
}

使用 super 引用成员方法

函数式接口

@FunctionalInterface
public interface GreetAble {
    void greet();
}

父类

public class Human {
 
    public void sayHello() {
        System.out.println("Hello!");
    }
 
}

子类

public class Man extends Human {
    @Override
    public void sayHello() {
        System.out.println("hey,bro!");
    }
 
    /**
     * 定义方法method,参数传递GreetAble接口
     * @param g 这里传入的是Lambda表达式
     */
    public void method(GreetAble g) {
        g.greet();
    }
 
    /**
     * 调用method方法,使用Lambda表达式
     */
    public void show(){
 
        // 创建Human对象,调用sayHello方法
        method(() -> { new Human().sayHello(); });
        
        // 简化Lambda
        method(() -> new Human().sayHello());
 
        // 使用super关键字代替父类对象
        method(() -> super.sayHello());

        // 再简化
        method(supper::sayHello);
        
    }
    
}

使用 this 引用成员方法

函数式接口

@FunctionalInterface
public interface RichAble {
    void buy();
}

一个方法要以函数式接口为参数

public class Husband {
    /**
     * 结婚
     * @param lambda 函数式接口,买东西
     */
    private void marry(RichAble lambda) {
        lambda.buy();
    }
 
    /**
     * 要开心
     */
    public void beHappy() {
        marry(() -> System.out.println("买套房子"));
    }
}

开心方法 beHappy 调用了结婚方法 marry ,后者的参数为函数式接口 Richable ,所以需要一个 Lambda 表达式。 但是如果这个 Lambda 表达式的内容已经在本类当中存在了,则可以对 Husband 丈夫类进行修改

public class Husband {
    /**
     * 买房子
     */
    private void buyHouse() {
        System.out.println("买套房子");
    }
 
    /**
     * 结婚
     * @param lambda 函数式接口,买东西
     */
    private void marry(RichAble lambda) {
        lambda.buy();
    }
 
    /**
     * 要开心
     */
    public void beHappy() {
        marry(() -> this.buyHouse());
    }
}

如果希望去掉 Lambda 表达式,可以使用 this 去引用成员方法

public class Husband03 {
    /**
     * 买房子
     */
    private void buyHouse() {
        System.out.println("买套房子");
    }
 
    /**
     * 结婚
     * @param lambda 函数式接口,买东西
     */
    private void marry(RichAble lambda) {
        lambda.buy();
    }
 
    /**
     * 要开心
     */
    public void beHappy() {
        marry(this::buyHouse);
    }
}

类构造器引用

方法引用其实还好,至少方法名是固定的。但是构造器的名字与类名相同,根本不固定,所以可以通过类名称::new的方式引用

一个简单的类

public class Person {
 
    private String name;
 
    public Person(String name) {
        this.name = name;
    }
 
    public String getName() {
        return this.name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
}

函数式接口

@FunctionalInterface
public interface PersonBuilder {
    /**
     * 创建 Person 对象
     * @param name Person对象名
     * @return Person对象
     */
    Person buildPerson(String name);
}

要使用这个接口就需要通过 Lambda 表达式

public class Demo {
 
    public static void main(String[] args) {
        printName("Tom", (name) -> new Person(name));
    }
 
    public static void printName(String name, PersonBuilder builder) {
        System.out.println(builder.buildPerson(name).getName());
    }
 
}

如果换成类构造器引用,就得这么写

public class Demo {
 
    public static void main(String[] args) {
        printName("Tom", Person::new);
    }
 
    public static void printName(String name, PersonBuilder builder) {
        System.out.println(builder.buildPerson(name).getName());
    }
 
}

如果是数组,那写法就有些区别了

函数式接口

@FunctionalInterface
public interface ArrayBuilder {
    /**
     * 创建数组的函数式接口
     * @param length 数组长度
     * @return 存储int类型的数组
     */
    int[] buildArray(int length);
}

使用 Lambda 表达式应用接口

public class Demo {
 
    public static void main(String[] args) {
        int[] array = initArray(10, length -> new int[length]);
    }
 
    private static int[] initArray(int length, ArrayBuilder builder) {
        return builder.buildArray(length);
    }
 
}

使用构造器引用应用接口

public class Demo {
 
    public static void main(String[] args) {
        int[] array = initArray(10, int[]::new);
    }
 
    private static int[] initArray(int length, ArrayBuilder builder) {
        return builder.buildArray(length);
    }
 
}

很显然,数组的构造器引用就不能通过类名了,这个需要注意

写在

看到过其他人的文章中有一句话形容 Lambda 表达式非常贴切

Lambda 表达式的原则是“可推导就是可省略”

关于 Lambda 表达式我认为我学习到的内容才是冰山一角,很多东西可能还需要在以后的实践过程中再去熟悉再去体会,后续有其他关于 Lambda 的想要分享的内容还是会在博客中分享



参考内容
Java基础——Lambda表达式
理解 Java 方法引用(方法引用符:“双冒号 :: ”)

标签:Java,String,int,void,public,表达式,Lambda
来源: https://www.cnblogs.com/colee51666/p/16449734.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有