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 給它,否則就會 !@#$%^&……

如果真的有人多手加多一個 public abstract method,會怎樣呢?
其實現代的 IDE 如 Eclipse 、NetBean 和 RAD ( 新版支持 Java 8 未推出,不過我估正常都會做到的 ) 等都會標示錯誤,而 Compile 時亦會報告錯誤。

但您是無須要一定要把@FunctionalInterface 加在每一個 Functional Interface 上,它的作用跟 @Override 類似,加與不加不會直接影響 Lambda Expression 的操作,加了只是一個 best practice ,讓其他人清楚了解這個 interface 的獨特意義。

下篇再續,如有問題,請留言給我,謝謝!

Java Lambda 教學 Part 1
Tagged on:                 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

zh_HKChinese (Hong Kong)