Lambda 表达式

Lambda 表达式

  Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使 Java 的语言表达能力得到了提升。

Lambda表达式.png

语法格式

① 无参,无返回值

// 原始写法
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("原始写法!");
    }
};
runnable.run();

// Lambda 表达式写法
Runnable runnableLambda = () -> {
    System.out.println("Lambda表达式!");
};
runnableLambda.run();

② 需要一个参数,但没有返回值

// 原始写法
Consumer<String> consumer = new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
};
consumer.accept("测试!");

// Lambda 表达式写法
Consumer<String> consumerLambda = (String s) -> {
    System.out.println(s);
};
consumerLambda.accept("Lambda表达式!");

③ 数据类型可以省略,因为可由编译器推断得出,称为"类型推断"

// Lambda 表达式写法
Consumer<String> consumerLambda = (String s) -> {
    System.out.println(s);
};
consumerLambda.accept("Lambda表达式!");

// 省略上面的数据类型
Consumer<String> consumerLambda = (s) -> {
    System.out.println(s);
};
consumerLambda.accept("类型省略!");

  Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的类型推断。

④ 若只需要一个参数时,参数的小括号可以省略

// Lambda 表达式写法
Consumer<String> consumerLambda = (String s) -> {
    System.out.println(s);
};
consumerLambda.accept("Lambda表达式!");

// 省略上面的小括号
Consumer<String> consumerLambda = s -> {
    System.out.println(s);
};
consumerLambda.accept("小括号省略!");

⑤ 需要两个及以上参数,多条执行语句,并且可以有返回值

// 原始写法
Comparator<Integer> comparator = new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        System.out.println("o1 = " + o1);
        System.out.println("o2 = " + o2);
        return o1.compareTo(o2);
    }
};

// Lambda 表达式写法
Comparator<Integer> comparatorLambda = (o1, o2) -> {
    System.out.println("o1 = " + o1);
    System.out.println("o2 = " + o2);
    return o1.compareTo(o2);
};
System.out.println("comparatorLambda.compare(12, 20) = " + comparatorLambda.compare(12, 20));

⑥ 当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略

// 原始写法
Comparator<Integer> comparator = new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1.compareTo(o2);
    }
};

// Lambda 表达式写法
Comparator<Integer> comparatorLambda = (o1, o2) -> {
    return o1.compareTo(o2);
};
System.out.println("comparatorLambda.compare(12, 20) = " + comparatorLambda.compare(12, 20));

Comparator<Integer> comparatorLambdaOmit = (o1, o2) -> o1.compareTo(o2);
System.out.println("comparatorLambdaOmit.compare(12, 20) = " + comparatorLambdaOmit.compare(12, 20));

// 方法引用
Comparator<Integer> comparatorLambdaOmit = Integer::compareTo;

Lambda 表达式的本质

① Lambda 表达式作为接口的实现类的对象;
② Lambda 表达式是一个匿名函数;

Lambda 表达式是如何实现的?

Labmda 表达式不是匿名内部类的语法糖,但是他也是一个语法糖。实现方式其实是依赖了几个 JVM 底层提供的 lambda 相关 API。

代码举例:

  为啥说他并不是内部类的语法糖呢?前面讲内部类我们说过,内部类在编译之后会有两个 class 文件,但是,包含 lambda 表达式的类编译后只有一个文件。   可以看到,在 forEach 方法中,其实是调用了 java.lang.invoke.LambdaMetafactory#metafactory 方法,该方法的第 5 个参数 implMethod 指定了方法实现。可以看到这里其实是调用了一个 lambdamainmain0 方法进行了输出。

再来看一个稍微复杂一点的,先对 List 进行过滤,然后再输出:

两个 lambda 表达式分别调用了 lambda$main1 和 lambda\main$0 两个方法。

  所以,lambda 表达式的实现其实是依赖了一些底层的 api,在编译阶段,编译器会把 lambda 表达式进行解糖,转换成调用内部 api 的方式。

函数式接口

如果接口中只声明一个抽象方法,则此接口就称为函数式接口。因为只有给函数式接口提供实现类的对象时,才可以使用 Lambda 表达式。

API 中函数式接口所在的包

JDK8 中声明的函数式接口在java.util.function包下

四大核心函数式接口:

函数式接口
称谓
参数类型
用途
Consumer<T>消费型接口T对类型 T 的对象应用操作,包含方法:void accept(T t)
Supplier<T>供给型接口无返回类型为 T 的对象,包含方法:T get()
Function<T, R>函数型接口T对类型为 T 的对象应用操作,并返回结果。结果是 R 类型的对象,包含方法:R apply(T t)
Predicate<T>判断型接口T确定类型为 T 的对象是否满足某约束,并返回 boolean 值,包含方法:boolean test(T t)