2014年5月7日

Java 8 Lambda Expressions

Java 8從 2014/3/18 正式推出至今,也一個多月了,而Eclipse也在Java 8推出後宣布Eclipse 4.3.2將會支援Java8,因此花點時間測試了一下最有興趣的新特性,也就是本篇的標題,Lambda Expressions。

環境準備:

要用Eclipse來測試Java 8,需要先準備好Java 8相關的開發工具。

  1. Java Platform (JDK) 8u5,此為Java的開發工具,目前版本為8u5。

  2. eclipse 4.3.2(kepler SR2),Eclipse要4.3.2版才會支援Java 8,由於我是單純要測試而已,因此只抓標準版的Eclipse即可。

  3. Eclipse JDT升級,才能將專案的compiler設為1.8。安裝完Eclipse 4.3.2之後,再到:
    Help -> Eclipse Marketplace,搜尋Java 8,安裝Java 8 support for Eclipse Kepler SR2

  4. 開一個新的project,並將project設定為 compiler 1.8。

做完以上的準備,可以開始測試Java 8了。

Lambda語法(Syntax)介紹:

Lambda語法架構為:

Argument List Arrow Token Body
(int x, int y) -> x+y

下面有幾個範例,來對Lambda有基本的認識:

輸入沒有任何參數,輸出2:

() -> 2;  

輸入兩個int參數,輸出兩個參數相加的結果:

(int x, int y) -> x+y;

輸入字串,在console顯示輸入字串:

(String s) -> System.out.println(s);

Functional Interfaces:

對於Lambda的語法有了了解之後,接著來看看Java 8如何使用它。Java 8引進的一個新的詞彙,Functional Interfaces,指的是只擁有單一抽象方法的介面。這詞彙並不是新概念,我們之前在開發時也常常會使用到類似的介面,如比較資料時會用到的Comparator介面,或是在跑執行緒時會用到的Runnable介面。

而只要是Functional Interfaces,就可以用Lambda的方式來實作!比如說Runnable介面,原本可以透過匿名函式的方式實作,現在可改用Lambda的方式來實作,如下:

Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("run!");
    }
};

Runnable r2 = () -> System.out.println("run!"); 

再來看看Comparator介面,用匿名函式與用Lambda的實作方式:

Comparator<Integer> c1 = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1.compareTo(o2);
        }
    };

Comparator<Integer> c2 = (o1, o2) -> o1.compareTo(o2);

透過Lambda來實作可以讓程式可以精簡很多,並增加程式的可讀性。

另外,Java 8也引進了新的標注(Annotation),來標記介面為Functional Interface,只要該介面有兩個以上的自定義方法,則編譯器會報錯,此標注讓團隊的其他開發人員不會在此介面上加上別的方法。

@FunctionalInterface
interface Ti {
    public void test(String s);
}

java.util.function:

Java的型別需要事先定義好,程式才能進行編譯。因此為了搭配Lambda語法,Java 8提供新的介面讓人使用,這些介面統一放在java.util.function底下,每個介面都代表一個方法,根據輸入參數與輸出參數,定了多個介面出來。以下介紹幾個基本類型的介面:

  • Consumer< T >:輸入參數類型為T,沒有任何輸出。
  • Function< T, R >:輸入參數類型為T,輸出參數類型為R。
  • Predicate < T >:輸入參數類型為T,輸出一個布林值。
  • Supplier< T >:沒有任何輸入,輸出參數類型為T。
  • UnaryOperator< T >:輸入參數類型為T,輸出參數類型為T。

舉例來說,今天我需要一個接受字串作為輸入而沒有任何輸出的方法,則我可以使用Consumer這個介面來操作,如下:

Consumer<String> c = (s) -> System.out.println(s);
c.accept("hello world!"); // output hello world!

ArrayList與java.util.function:

接著要談到於Java 8的Collections中,新增了幾個方法可以搭配Lambda使用。這邊會說明ArrayList的三個方法:

  • forEach(Consumer<? super E> action)
  • removeIf(Predicate<? super E> filter)
  • replaceAll(UnaryOperator operator)

首先介紹forEach方法,就跟for each語法一樣,只是這邊更直覺。傳入參數為Consumer介面,Consumer介面為單一輸入參數,沒有任何輸出參數,下面為範例:

List<Integer> list = new ArrayList<>();
Collections.addAll(list, 7, 6, 2, 3);

// for each語法
for(Integer t : numbers) {
    System.out.println(t);
}

// Lambda語法
numbers.forEach((t) -> {
    System.out.println(t);
});

接著介紹removeIf方法,傳入參數為Predicate介面,此介面輸入參數類型為T,然後輸出一個布林值。也就是說,他會幫你繞行陣列的每個元素,元素會被丟到Predicate內執行,當執行結果為true,則該元素會被刪除,範例如下:

List<Integer> list = new ArrayList<>();
Collections.addAll(list, 7, 6, 2, 3);
System.out.println(list); // output:[7, 6, 2, 3]
list.removeIf((n) -> {
    if (n > 3) {
        return true;
    } else {
        return false;
    }
});
System.out.println(list); // output:[2, 3]

最後是replaceAll方法,傳入參數為UnaryOperator介面,此介面輸入參數類型和輸出參數類型是一樣的,這邊運作的方法是,回傳的參數會取代掉輸入的參數,範例如下:

List<Integer> list = new ArrayList<>();
Collections.addAll(list, 7, 6, 2, 3);
System.out.println(list); // output:[7, 6, 2, 3]
list.replaceAll((t) -> t + 1);
System.out.println(list); // output:[8, 7, 3, 4]

Method and Constructor References:

在Java 8,你可以將將方法的參考傳給變數,摘錄JSR335的內容:

Examples of method and constructor references:
System::getProperty
"abc"::length
String::length
super::toString
ArrayList::new
int[]::new

說明一下上面的例子:

  1. 如果你要需要靜態方法的參考,則可以使用 類別名稱::靜態方法名稱 來得到參考。
  2. 如果你是需要某個實例物件的方法他的參考,則可以用 實例變數::方法名稱 來得到參考。
  3. 如果你需要得到的是建構式,則可以使用 類別名稱::new 來得到參考。

下面為一個簡單的範例,透過Lambda實作一個介面,之後將介面的方法傳給變數,在用此變數呼叫該方法。

public static void main(String[] args) {
    TestInterface ti = (i) -> (i > 5) ? true : false;
    Predicate<Integer> p = ti::isGreaterThenFive;

    System.out.println(p.test(6));
}

@FunctionalInterface
interface TestInterface {
    public boolean isGreaterThenFive(Integer i);
}

參考:

Java Language Specification - 9.8 Functional Interfaces

Java SE 8: Lambda表达式

Jave SE 8: Lambda Quick Start

ArrayList(Java Platform SE 8)

Introduction to Functional Interfaces – A concept recreated in Java 8

Lambda Specification, Part C: Method and Constructor References