介绍

lambda 表达式本质上是一个匿名方法,用于实现函数式接口(functional interface)定义的方法。这就产生了匿名类。lambda 也称为闭包(closures)。

函数式接口是仅有一个抽象方法的接口。如 Runnable 接口仅有 run() 方法,该方法表明了接口的目的。函数式接口定义了 lambda 表达式的目标类型,lambda 表达式仅可用于特定的目标类型的上下文中。函数式接口又称 SAM 类型,SAM 指 Single Abstract Method。

Object 类中的公有方法隐式是函数式接口的成员,因为函数式接口的实例自动实现了这些方法。

-> 称为 lambda 操作符或箭头操作符,它将 lambda 表达式划分成了两个部分。左边是参数,参数可为空;右边是 lambda 体,表示 lambda 表达式的行为。lambda 体可以是单个表达式,也可以是块。

lambda 表达式出现在目标类型上下文中时,自动创建实现了函数式接口的类实例,lambda 表达式作为函数式接口中抽象方法的实现。lambda 表达式的参数类型和返回类型、参数个数必须和函数式接口中抽象方法定义的兼容、对应。

interface A {
  double get();
}

// lambda 表达式作为 get() 方法的实现
A a = () -> 1.2;

// 返回 1.2
a.get();

lambda 表达式中的参数可以指定类型,也可以不指定根据上下文自行推断。

函数式接口类型变量可以执行和它兼容的任意 lambda 表达式。

当 lambda 表达式仅有一个参数时,加不加小括号都可以。n -> n * 2 等价于 (n) -> n * 2

对于多参数 lambda 表达式,如果想为参数声明类型,则必须为所有参数声明类型。

块 lambda 表达式

-> 右边为块的 lambda 表达式称为块 lambda 表达式。使用块 lambda 表达式必须使用 return 语句。lambda 块本质上类似于方法体。

泛型函数接口

函数式接口可以是泛型接口,此时关联的 lambda 表达式的类型由泛型接口中的类型指定。

interface A<T> {}

// 类型为 Integer
A<Integer> a = (...) -> {...};

// 类型为 String
A<String> a = (...) -> {...};

lambda 表达式作参数

能接收 lambda 表达式的参数,类型必须是可以和该 lambda 表达式兼容的函数式接口。

lambda 表达式和异常

lambda 表达式中抛出异常时,函数式接口中抽象方法的 throws 子句必须有对应的异常。

lambda 表达式和变量捕获

在 lambda 表达式中,可以访问 lambda 表达式自身所在的作用域中定义的静态变量、类实例、表示该作用域的 this 等。在 lambda 表达式中访问该作用域中的局部变量的过程称为变量捕获(variable capture)。能够访问的局部变量要么是 effectively final 变量,要么是 final 修饰的变量。effectively final 变量是赋过初值之后值没有变化的变量。在 lambda 表达式中不能修改局部变量的值,在 lambda 表达式自身所在的作用域内也不能修改,因为这样做变量将不再是 effectively final。

方法引用

方法引用可以引用一个方法但不执行它。方法引用和 lambda 表达式有关,因为它也需要一个兼容函数式接口的目标类型上下文。方法引用可以创建函数式接口的实例。

静态方法的方法引用一般形式为:ClassName::methodName,可以在目标类型兼容的任何地方使用。
实例方法的方法引用一般形式为:objRef::methodName

如果想指定一个给定类的任意实例都可以使用的实例方法,而不是某个特定的类,可以这样表示方法引用:ClassName::instanceMethodName,这个方法引用对应的函数式接口中的方法需要两个参数,第一个参数匹配调用对象本身,即 ClassName 实例;第二个参数匹配 instanceMethodName 需要的参数。

interface MyFunc<T> {
    boolean func(T v1, T v2);
}

class High {
    private int h;
    public High(int h) {
        this.h = h;
    }
    boolean same(High high) {
        return h == high.h;
    }
}

public class InstanceMethod {
    static <T> int counter(T[] vals, MyFunc<T> f, T v) {
        int count = 0;
        for (int i = 0; i < vals.length; i++) {
            if (f.func(vals[i], v)) {
                count++;
            }
        }
        return count;
    }

    public static void main(String[] args) {
        int count;
        High[] week = {new High(10), new High(20), new High(31)};
        // same 与 MyFunc 接口兼容,调用 same 方法的对象作为
        // func 函数的第一个参数,传入 same 方法的对象作为 func
        // 函数的第二个参数
        count = counter(week, High::same, new High(10));
        System.out.println(count);
    }
}

泛型类、泛型方法也可以作为方法引用。泛型方法的方法引用形式为:ClassName::<Type>method,泛型类的方法引用形式为:Class<Type>::method

构造器引用

构造器引用一般形式为:Class::new,可以赋值给与构造器兼容的函数式接口类型的变量。
泛型类、泛型方法的使用与前述类似,只不过方法名是 new
数组的构造器引用形式为:type[]::new

预定义的函数接口

接口 描述
UnaryOperator<T> 对 T 类型对象使用一元操作符,返回 T 类型对象,方法为 apply()
BinaryOperator<T> 对两个 T 类型对象使用二元操作符,返回 T 类型对象,方法为 apply()
Consumer<T> 对 T 类型对象进行操作,方法为 accept()
Supplier<T> 返回 T 类型对象,方法为 get()
Function<T, R> 对 T 类型对象进行操作,返回 R 类型对象,方法为 apply()
Predicate<T> 判断 T 类型对象是否满足某个条件,据此返回 boolean 值,方法为 test()

参考

[1] Herbert Schildt, Java The Complete Reference 11th, 2019.

 posted on 2024-04-22 16:16  x-yun  阅读(2)  评论(0编辑  收藏  举报