Java lambdas 简介

功能接口

Lambdas 只能在一个功能界面上运行,这是一个只有一个抽象方法的界面。功能接口可以具有任意数量的 defaultstatic 方法。 (因此,它们有时被称为单抽象方法接口或 SAM 接口)。

interface Foo1 {
    void bar();
}

interface Foo2 {
    int bar(boolean baz);
}

interface Foo3 {
    String bar(Object baz, int mink);
}

interface Foo4 {
    default String bar() { // default so not counted
        return "baz";
    }
    void quux();
}

在声明功能界面时,可以添加 @FunctionalInterface 注释。这没有特殊效果,但如果将此注释应用于不起作用的接口,则会生成编译器错误,从而提醒你不应更改接口。

@FunctionalInterface
interface Foo5 {
    void bar();
}

@FunctionalInterface
interface BlankFoo1 extends Foo3 { // inherits abstract method from Foo3
}

@FunctionalInterface
interface Foo6 {
    void bar();
    boolean equals(Object obj); // overrides one of Object's method so not counted
}

相反,这不是一个功能接口,因为它有多个抽象方法:

interface BadFoo {
    void bar();
    void quux(); // <-- Second method prevents lambda: which one should 
                 // be considered as lambda?
}

也不是一个功能界面,因为它没有任何方法:

interface BlankFoo2 { }

请注意以下事项。假设你有

interface Parent { public int parentMethod(); }

interface Child extends Parent { public int ChildMethod(); }

然后 Child 不能是一个功能接口,因为它有两个指定的方法。

Java 8 还在包 java.util.function提供了许多通用的模板化功能接口。例如,内置接口 Predicate<T> 包含一个方法,该方法输入 T 类型的值并返回 boolean

Lambda 表达式

Lambda 表达式的基本结构是:

FunctionalInterface fi =() - \> System.out.println(Hello);

然后 fi 将保存一个类的单例实例,类似于一个匿名类,它实现了 FunctionalInterface,其中一个方法的定义是 { System.out.println("Hello"); }。换句话说,以上几乎相当于:

FunctionalInterface fi = new FunctionalInterface() {
    @Override
    public void theOneMethod() {
        System.out.println("Hello");
    }
};

lambda 只与匿名类大部分等价,因为在 lambda 中,thissupertoString() 等表达式的含义引用了赋值发生的类,而不是新创建的对象。

使用 lambda 时无法指定方法的名称 - 但是你不需要,因为功能接口必须只有一个抽象方法,因此 Java 会覆盖该方法。

如果 lambda 的类型不确定(例如重载方法),你可以向 lambda 添加一个强制转换来告诉编译器它的类型应该是什么,如下所示:

Object fooHolder = (Foo1) () -> System.out.println("Hello");
System.out.println(fooHolder instanceof Foo1); // returns true

如果函数接口的单个​​方法接受参数,则这些参数的本地正式名称应出现在 lambda 的括号之间。没有必要声明参数的类型或返回,因为这些是从接口获取的(尽管如果你想要声明参数类型不是错误)。因此,这两个例子是等价的:

Foo2 longFoo = new Foo2() {
    @Override
    public int bar(boolean baz) {
        return baz ? 1 : 0;
    }
};
Foo2 shortFoo = (x) -> { return x ? 1 : 0; };

如果函数只有一个参数,则可以省略参数周围的括号:

Foo2 np = x -> { return x ? 1 : 0; }; // okay
Foo3 np2 = x, y -> x.toString() + y // not okay

隐式的回报

如果放在 lambda 中的代码是 Java 表达式而不是语句,则将其视为返回表达式值的方法。因此,以下两个是等效的:

IntUnaryOperator addOneShort = (x) -> (x + 1);
IntUnaryOperator addOneLong = (x) -> { return (x + 1); };

访问局部变量(值闭包)

由于 lambdas 是匿名类的语法简写,因此它们遵循相同的规则来访问封闭范围中的局部变量; 必须将变量视为 final 并且不在 lambda 内修改。

IntUnaryOperator makeAdder(int amount) {
    return (x) -> (x + amount); // Legal even though amount will go out of scope
                                // because amount is not modified
}

IntUnaryOperator makeAccumulator(int value) {
    return (x) -> { value += x; return value; }; // Will not compile
}

如果需要以这种方式包装更改变量,则应使用保留变量副本的常规对象。使用 lambda 表达式Java Closures 中阅读更多内容

接受 Lambda

因为 lambda 是接口的实现,所以不需要做任何特殊的事情来使方法接受 lambda:任何带有功能接口的函数也可以接受 lambda。

public void passMeALambda(Foo1 f) {
    f.bar();
}
passMeALambda(() -> System.out.println("Lambda called"));

Lambda 表达式的类型

lambda 表达式本身没有特定的类型。虽然参数的类型和数量以及返回值的类型确实可以传达某些类型信息,但这些信息只会限制它可以分配给哪些类型。当 lambda 以下列方式之一分配给功能接口类型时,它会收到一个类型:

  • 直接分配到功能类型,例如 myPredicate = s -> s.isEmpty()
  • 将其作为具有功能类型的参数传递,例如 stream.filter(s -> s.isEmpty())
  • 从返回函数类型的函数返回它,例如 return s -> s.isEmpty()
  • 将其转换成功能型,例如 (Predicate<String>) s -> s.isEmpty()

在对函数类型进行任何此类赋值之前,lambda 没有明确的类型。为了说明,请考虑 lambda 表达式 o -> o.isEmpty()。可以将相同的 lambda 表达式分配给许多不同的功能类型:

Predicate<String> javaStringPred = o -> o.isEmpty();
Function<String, Boolean> javaFunc = o -> o.isEmpty();
Predicate<List> javaListPred = o -> o.isEmpty();
Consumer<String> javaStringConsumer = o -> o.isEmpty(); // return value is ignored!
com.google.common.base.Predicate<String> guavaPredicate = o -> o.isEmpty();

现在它们已被分配,所示的示例具有完全不同的类型,即使 lambda 表达式看起来相同,并且它们不能彼此分配。