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 表示式看起來相同,並且它們不能彼此分配。