[轉]Java 8 的 lambda 表示式 Java 8 的 lambda 表示式

流星線上發表於2012-08-01

Java 8 預計將在 2013 年釋出,Java 8 將支援 Lambda 功能,儘管該規範還在不斷的變化,但是 Java 8 的開發版已經實現了對 lambda 的支援。

關於 lambda 表示式的定義請看維基百科

該文章將帶你熟悉 lambda 語法,以及使用集合 API 中的 lambda 以及相關的語言增強,本文所有的程式碼都是在 JDK 8 lambda build b39 編譯。

功能介面

只包含一個方法的介面被稱為功能介面,Lambda 表示式用用於任何功能介面適用的地方。

java.awt.event.ActionListener 就是一個功能介面,因為它只有一個方法:void actionPerformed(ActionEvent). 在 Java 7 中我們會編寫如下程式碼:

button.addActionListener(new ActionListener() {   

      public void actionPerformed(ActionEvent e) {   

         ui.dazzle(e.getModifiers());  

     }  

  });

而 Java 8 中可以簡化為:

button.addActionListener(e -> { ui.dazzle(e.getModifiers()); });

編譯器知道lambda 表示式必須符合 void actionPerformed(ActionEvent) 方法的定義。看起來 lambda 實體返回 void,實際上它可以推斷出引數 e 的型別是 java.awt.event.ActionEvent.

函式集合

Java 8 的類庫包含一個新的包 java.util.functions ,這個包中有很多新的功能介面,這些介面可與集合 API 一起使用。

java.util.functions.Predicate

使用謂詞 (Predicate) 來篩選集合:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Dave");  

   List<String> filteredNames = names  

         .filter(e -> e.length() >= 4)  

         .into(new ArrayList<String>());  

 for (String name : filteredNames) {  

     System.out.println(name);  

 }

這裡我們有兩個新方法:

 -  Iterable<T> filter(Predicate<? super T>) 用於獲取元素滿足某個謂詞返回 true 的結果
 -  <A extends Fillable<? super T>> A into(A) 將用返回的結果填充 ArrayList

java.util.functions.Block

我們可使用一個新的迭代器方法來替換 for 迴圈 void forEach(Block<? super T>):

 List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Dave");  

  names  

    .filter(e -> e.length() >= 4)  

    .forEach(e -> { System.out.println(e); });

forEach() 方法是 internal iteration 的一個例項:迭代過程在 Iterable 和 Block 內部進行,每次可訪問一個元素。 最後的結果就是用更少的程式碼來處理集合:

 List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Dave");  

 names  

    .mapped(e -> { return e.length(); })  

    .asIterable() // returns an Iterable of BiValue elements  

               // an element's key is the person's name, its value is the string length  

    .filter(e -> e.getValue() >= 4)  

    .sorted((a, b) -> a.getValue() - b.getValue())  

    .forEach(e -> { System.out.println(e.getKey() + '\t' + e.getValue()); }); 

這樣做的優點是: 
元素在需要的時候才進行計算 
如果我們取一個上千個元素的集合的前三條時,其他元素就不會被對映 
鼓勵使用方法鏈 
我們無需才儲存中間結果來構建新的集合 
內部迭代過程因此大多數細節 
例如,我們可以通過下面程式碼來並行 map() 操作 
writing myCollection.parallel().map(e ‑> e.length()). 

方法引用

我們可通過 :: 語法來引用某個方法。方法引用被認為是跟 lambda 表示式一樣的,可用於功能介面所適用的地方。

我們可以引用一個靜態方法:

executorService.submit(MethodReference::sayHello);  

 private static void sayHello() {  

         System.out.println("hello");  

 } 

或者是一個例項的方法:

Arrays.asList("Alice", "Bob", "Charlie", "Dave").forEach(System.out::println); 

我們也可以建立工程方法並將構造器引用賦值給 java.util.functions.Factory:

    Factory<Biscuit> biscuitFactory = Biscuit::new;  

     Biscuit biscuit = biscuitFactory.make(); 

最後,我們建立一個引用到隨意例項的例子:

 interface Accessor<BEAN, PROPERTY> {  

         PROPERTY access(BEAN bean);  

 }      

 public static void main(String[] args) {  

         Address address = new Address("29 Acacia Road", "Tunbridge Wells");  

         Accessor<Address, String> accessor = Address::getCity;  

         System.out.println(accessor.access(address));  

 } 

這裡我們無需繫結方法引用到某個例項,我們直接將例項做為功能介面的引數進行傳遞。

預設方法

直到今天的 Java ,都不可能為一個介面新增方法而不會影響到已有的實現類。而 Java 8 允許你為介面自身指定一個預設的實現:

interface Queue {

     Message read();  

     void delete(Message message);  

     void deleteAll() default {  

             Message message;  

             while ((message = read()) != null) {  

                     delete(message);  

             }  

     }  

}

子介面可以覆蓋預設的方法:

interface BatchQueue extends Queue {  

         void setBatchSize(int batchSize);  

         void deleteAll() default {  

                setBatchSize(100);  

                 Queue.super.deleteAll();  

         }  

 } 

或者子介面也可以通過重新宣告一個沒有方法體的方法來刪除預設的方法:

interface FastQueue extends Queue {  

         void deleteAll();  

 } 

這個將強制所有實現了 FastQueue 的類必須實現 deleteAll() 方法。

HotSpot 實現

lambda 不只是可以減少很多程式碼的編寫,其位元組碼和執行時的實現也比 Java 7 中的匿名類的效率更高。針對每一個 lambda 表示式,編譯器都會建立一個對應的形如 lambda$1() 這樣的方法。這個過程被稱之為 lambda body desugaring. 當遇見一個 lambda 表示式,編譯器將會發起一個 invokedynamic 呼叫,並從目標功能介面中獲取返回值。

深入閱讀

本文很多內容都基於 Brian Goetz 的文章:State of the Lambda, State of the Lambda: Libraries Edition and Translation of Lambda Expressions. 這些文字詳細描述了 lambda 語法、變數捕獲、型別介面和編譯等內容。

英文原版 轉自oschina

相關文章