【語言】Java 8新特性總結

2018.12.02

前言

雖然Java 8早在2014年就已經發布了,但得益於它所帶來的新特性,使得Java重新煥發生機。

Java 8包含如下新特性1

  • Lambda表達式
  • 方法引用
  • 默認方法
  • 新的Streams API
  • Optional
  • 新的Date/Time API
  • Narshorn,新的JS引擎
  • 移除永久代(Permanent Generation)
  • … …

Lambda表達式

定義

Lambda表達式,可以說就是爲單個方法的匿名類所提供的語法糖。Lambda表達式的支持有助於簡化Java代碼。編譯器會根據Lambda表達式的上下文來判斷所使用的***函數式接口***和參數的類型。對於特定方法,還能用***方法引用***進一步地簡化Lambda表達式的寫法。

語法

主要的語法就是“參數 -> 方法體”。Lambda表達式還有四條較爲重要的語法:

  • 參數類型的聲明是可選的
  • 當只有一個參數時,參數兩側的括號是可選的
  • 方法體使用花括號是可選的(除非方法體包含多條語句)
  • 當使用單個表達式返回返回值時,return關鍵字是可選的
Arrays.sort(strArray, (foo, bar) -> foo.length() - bar.length());

上面的例子中,Lambda表達式實現了Comparator接口完成排序。

作用域

Lambda表達式中能引用final變量或或者實際final變量,所謂實際final變量就是變量僅被賦值一次。例如:

// 正例
String sql = "delete * from User";
getHibernateTemplate().execute(session ->
	session.createSQLQuery(sql).uniqueResult());
// 反例
String sql = "delete * from User";
getHibernateTemplate().execute(session ->
	session.createSQLQuery(~~sql~~ ).uniqueResult());
sql = "select * from User";

Lambda表達式的極簡模式——方法引用

定義

方法引用,引用的是已存在的方法,可以算是Lambda表達式的一種簡化寫法。對於方法體中只調用某個已存在方法的Lambda表達式,就可以改寫爲直接引用該方法。

語法

通過::進行方法引用,以下方法可被引用2

  • 靜態方法:ContainingClass::staticMethodName
  • 類實例的實例方法:containingObject::instanceMethodName
  • 類的實例方法:全稱是“特定類型的任意對象的實例方法”,ContainingType::methodName
  • 類/數組構造器(ie. TreeSet::new/TreeSet[]:new):ClassName::new

“特定類型的任意對象的實例方法”,這句話容易和“類實例的實例方法”混淆。先說“類實例的實例方法”,很直觀,就是通過某個類實例引用它的實例方法,比如要對一個數組Clazz[] clazzArr排序,我們可以實例化了一個Comparator<Clazz> comparator並引用它的comparator::compare方法進行排序;再說“特定類型的任意對象的實例方法”,還是對Clazz[] clazzArr排序,但此時Clazz實現了Comparable接口,定義了Clazz::compareTo方法,那對於數組裏的任意對象,都可以直接地引用它們自身的compareTo實例方法比較大小,也就是引用Clazz類的任意對象的實例方法。

但從使用者的角度來說,“特定類型的任意對象的實例方法”,其實就是通過類名引用實例方法——ContainingType::methodName,所以我們可以直觀地理解爲“類的實例方法”。

Lambda表達式的類型——函數式接口

定義

既然有了Lambda表達式,那在Java裏如果聲明它們呢?於是就有了函數式接口。函數式接口是隻包含一個方法的接口,每個Lambda表達式,編譯器最後都會根據上下文判斷它所對應的函數式接口。

語法

Java 8在java.util.function包中定義幾個函數式接口:

  • Function<T, R>:接收T類型的對象,並返回R
  • Supplier<T>:返回T類型的對象
  • Predicate<T>:基於T類型的輸入,返回一個布爾值
  • Consumer<T>:對T類型的對象執行一定的動作
  • BiFunction<T, U, R>
  • BiConsumer<T, U, R>

示例如下:

// Function<T, R>
Function<String, Integer> length = String::length;
System.out.println(length.apply("GHD")); // 輸出3
// Consumer<T>
User user = new User("GHD");
Consumer<User> userConsumer = foo -> foo.setName("HD G.");
userConsumer.accept(user; // public interface Consumer<T> { void accept(T t);}
System.out.println(user.getName); // 輸出HD G.

Lambda鐵蹄踏遍Java

最適合應用Lambda表達式的場景,莫過於集合類的操作。前朝功臣Iterator利用hasNextnext也曾立下過汗馬功勞,但寫法不簡練,並且不易於並行化。Java 8要順利地推廣Lambda表達式,就需要先從集合類下手。然而事情沒有這麼簡單,java.util.Collection是個接口,不能實現方法,於是Java 8就引入了***默認方法***來解決這個問題。集合類的操作就通過Stream API來拓展新特性。

默認方法

定義

默認方法,就是接口方法的默認實現。Java 8之所以要引入接口的默認方法,原因是在擴展java.util.Collection的特性時,考慮到可能產生的兼容性問題,例如如果直接給java.util.Collection添加一個新的方法,那麼所有實現了該接口的類,都需要實現新方法。於是在擴展類似java.util.Collection的接口時,Java 8允許在接口裏定義默認方法,這樣所有實現該接口的類都自動添加了新方法的實現,以此實現“向後兼容”(Backwards Compatibility / Virtual Extension Methods)。

那可否用靜態方法來實現“向後兼容”呢?答案是不行3。類的靜態方法不能被子類繼承,因此子類都無法實現靜態方法;調用靜態方法時,也就不能通過子類名調用父類的靜態方法。

示例如下:

public interface Example {
    default void newMethod() {
         System.out.println("new method is here");
     }
 }

多個接口的繼承問題——鑽石問題

如果一個類實現了兩個接口,而這兩個接口都包含一個相同方法簽名的默認方法,這種情況就類似於“鑽石問題”4。鑽石問題描述的是下圖所示的繼承關係,B、C類都覆蓋了A類的方法,而D類同時繼承了B、C類,那麼通過D實例在調用該方法時,調用的是B類還是C類的方法?

鑽石問題

  • 情況一:如果兩個父接口都包含一個相同方法簽名的默認方法,那在編譯過程就會直接報錯。要解決這個問題,就需要在子類裏顯式地覆蓋這個方法。覆蓋時,方法體內可以同時調用父接口的super來顯式地調用默認方法。

  • 情況二:如果類實現了一個包含默認方法的接口,同時繼承一個包含相同方法簽名的類,那麼編譯是可以通過的,子類會調用父類的方法,原因是“類優先”。之所以採用“類優先”的策略,原因可能也和“向後兼容”有關5,升級到Java 8後,即使被擴展的接口引入了新的默認方法,也要保證不會影響到那些實現該接口的類的正常使用。

接口的默認方法 V.S. 抽象類

接口 抽象類 備註
覆蓋方法中引用子類成員變量
提供便利方法(Convenience Methods)6 例如,ArraysConllections所提供的方法,以及工廠方法
有構造器
包含成員變量

接口的默認方法,本質上還是爲了實現“向後兼容”而出現的,爲Java 8和Lambda編程提供橋樑,與抽象類還是有比較大的區別的。

Stream API

Stream API包含兩類操作7:中間操作(Intermediate Operation)和終結操作(Terminal Operation)。Stream API是加載模式,中間操作不會立即執行,只有到遇到第一個終結操作,中間操作纔會執行。中間操作包括map/filter/flatmap等。


  1. What’s New in Java 8 ↩︎

  2. Oracle Java Method References ↩︎

  3. Difference between static and default methods ↩︎

  4. Multiple Inheritance ↩︎

  5. Dealing with the diamond problem in Java ↩︎

  6. Convenience Method ↩︎

  7. Java 8 Stream API ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章