Java Lambda Expression
Java 8 已於今年3月發佈,其中一個很重大的改進就是 Java Lambda Expression,讓 Java 平台正式支援 functional programming paradigm,甚至有評論認為,這是 Java 繼 2005 年推出 Annotation 之後,另一個最革命性的改進。
無論您是 Java Platform 的老鳥或新手,都建議學懂 Lambda Expression,因它將會徹底改變將來 Java 編程的手法。( 老鳥如果看不明新手的 code ,將來又如何做 code review 呢?)
Lambda Expression 例子
Map<String, Double> map = getOrders().collect(Order::getLineItems).aggregateBy(LineItem::getName, () -> 0.0, (result, lineitem) -> result + lineItem.getValue());
以上是一個 Java Lambda Expression 的例子,其中紅色的部份可以說是一些 syntactic sugar,現在看不懂不緊要,我會慢慢解釋給大家的。
Java 8 之前的 Sorting
從前如果我們有一個 ArrayList<Person> ,而 Person Class 有firstName 和 lastName 兩個 attribute,我們想用 lastName 依據英文字母排序起來,我們大概會這樣寫
Collections.sort(personList, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { return p1.getLastName().compareTo(p2.getLastName()); } });
Java 8 的 Sorting
現在您用 Lambda Expression 這樣寫
Collections.sort(persionList,
(p1, p2) -> p1.getLastName().compareTo(p2.getLastName()) );
除了 Collections.sort(),Lambda Expression 還可以利用到以下的例子:
// java.util.concurrent.Executor.execute(java.lang.Runnable) someExecutor.execute( () -> doSomethingAsRunnable() ); // java.awt.Button.addActionListener(java.awt.event.ActionListener); someButton.addActionListener( e -> onButtonClick() );
大家能否看出以上的例子有甚麼共通點呢?
Java 8 中並不是所以 method invocation 都可以利用到 Lambda Expression。簡單的一個原則是所牵涉的 method argument 必須為一個 Functional Interface 或叫做 Single Abstract Method (SAM),那麼原本的 method argument 就可以用 Lambda Expression 來取代。
Functional Interface
Functional Interface 的定義就是一個只有一個 public abstract method 的 Java Interface。
以上的例子中的 Comparator、Runnable、ActionListener,它們全都屬於 Functional Interface ,因為它們各自只有一個 public method,例如:
- int Comparator<T>.compare(T o1, T o2);
- void Runnable.run();
- void ActionListener.actionPerformed(ActionEvent e);
當我們發現 method 中的 argument 是一個 Functional Interface 的時候,我們就可以利用 Lambda Expression 的 basic form 去取代原本繁複的 anonymous class 語法:
//old way new SomeFunctionalInterface() { @Override public someReturnType someMethod(args) { body } } //new way (args) -> { body }
正如以下的例子
// Old way Arrays.sort(personArray, new Comparator<Person>() { @Override public int compare(Person p1, Person p2) { int retValue = p1.getLastName().compareTo(p2.getLastName()); if (retValue == 0) { retValue = p1.getFirstName().compareTo(p2.getFirstName()); } return retValue; } }); // New way Arrays.sort(personArray, (p1, p2) -> { int retValue = p1.getLastName().compareTo(p2.getLastName()); if (retValue == 0) { retValue = p1.getFirstName().compareTo(p2.getFirstName()); } return retValue; });
其他 Lambda Expression 語法
如果 body 只有一行 statement ,大家還可以省略 method body 中的 { },如下
(args) -> one-statement-body
例如:
// New way -- return keyword will be inferred from the method signature Arrays.sort(personArray, (p1, p2) -> p1.getLastName().compareTo(p2.getLastName()));
如果只有一個 argument,大家就可以省略當中的 ( ),如下
one-arg -> { body } or one-arg -> one-statement-body
例如:
// java.awt.Button.addActionListener(java.awt.event.ActionListener); someButton.addActionListener( event -> setBackground(Color.BLUE) );
拆解當中的把戲
由於 Lambda Expression 只可以應用於 Functional Interface ( 即只有一個 public method ),Java compiler 就可以從中推斷 ( type inferencing ) 得到需要多少個 method arguments 、 argument types 及 return type,從而進行編譯。
其實 Lamdba 只是一個 Java Object,是 VM 臨時創建出來的 anonymous class,大家可以用以下的代碼來驗証。
// Can pre-construct the comparator using Lambda Expression Comparator<Person> comparator = (p1, p2) -> p1.getLastName().compareTo(p2.getLastName()); Collections.sort(personList, comparator); // Print the class name System.out.println(comparator.getClass().getCanonicalName());
看到這裡,大家或許會說 Lambda 也不是甚麼驚人的創舉,只是一個 syntactic sugar、shorthand 的方法來寫 java code,沒有我吹得那麼偉大。
其實它還有更加強大的用途,例如可以讓 Compiler 和 JVM 更加有效地做 optimization 和利用多核心 CPU 去執行程序,而且它可以推動 functional programming paradigm ,實作一些 map-reduce 的 algorithm,為應付 Big-Data作出重大的貢獻,不過篇幅有限,留待日後的章節再跟大家分享。
現在,我還是先討論多一些有關 Lambda 的基礎知識吧。
Method References
上文的例子都是即時創建一條 Lambda Expression 去滿足 method invocation 的需求。其實,如果我們有一個 Java static method ,而剛巧它的 method signature 跟 Functional Interface 中的 Single Abstract Method (SAM) 完全一樣的話,我們是可以直接用那個 Java static method 去取代 Lambda Expression 去 invoke method。這樣做的話, Java 8 叫做 Method References,其語法例子如下:
public class Example { // Define an artitary static method matching the signature of Comparator<Person>.compare(); public static int returnSomeIntegerOnly(Person p1, Person p2) { return p1.getLastName().compareTo(p2.getLastName()); } public static void main(String[] args) { List<Person> personList = new ArrayList<Person>(); Collections.sort(personList, Example::returnSomeIntegerOnly); } }
其語法是
class-name::static-method-name
試想想,有了 Method Reference 這招殺著,很多舊或 3rd party 的 Java Code 都可以很方便 reuse 。
@FunctionalInterface Annotation
Java 8 還新加入的 @FunctionalInterface 這個 annotation,它可以讓我們標記一個 Functional Interface 的設計原委 ( Design Intent ),其用途如下
@FunctionalInterface public interface MyFuncInterface { public void onlyOneMethod(String a1, String a2); }
一個有 @FunctionalInterface 標示的 interface ,就可以聲明給其他人 (同事) 知道,這個 Interface 是打算用來做 Functional Interface ,就請大家千萬不要胡亂增加其他的 public abstract method 給它,否則就會 [email protected]#$%^&……
如果真的有人多手加多一個 public abstract method,會怎樣呢?
其實現代的 IDE 如 Eclipse 、NetBean 和 RAD ( 新版支持 Java 8 未推出,不過我估正常都會做到的 ) 等都會標示錯誤,而 Compile 時亦會報告錯誤。
但您是無須要一定要把@FunctionalInterface 加在每一個 Functional Interface 上,它的作用跟 @Override 類似,加與不加不會直接影響 Lambda Expression 的操作,加了只是一個 best practice ,讓其他人清楚了解這個 interface 的獨特意義。
下篇再續,如有問題,請留言給我,謝謝!