一、簡介

毫無疑問,Java 8是Java自Java 5(釋出於2004年)之後的最重要的版本。這個版本包含語言、編譯器、庫、工具和JVM等方面的十多個新特性。

在本文中我們將學習這些新特性,並用實際的例子說明在什麼場景下適合使用。

包含Java開發者經常面對的幾類問題:

  • 語言
  • 編譯器
  • 工具
  • 執行時(JVM)

二、Lambda表示式和函式式介面

  Lambda表示式(也稱為閉包)是Java 8中最大和最令人期待的語言改變。它允許我們將函式當成引數傳遞給某個方法,或者把程式碼本身當作資料處理:函式式開發者非常熟悉這些概念。

  很多JVM平臺上的語言(Groovy、Scala等)從誕生之日就支援Lambda表示式,但是Java開發者沒有選擇,只能使用匿名內部類代替Lambda表示式。

  Lambda的設計耗費了很多時間和很大的社群力量,最終找到一種折中的實現方案,可以實現簡潔而緊湊的語言結構。

1、Lamda表示式語法

首先不看Lambda本身的寫法,可以發現,對於i值的訪問,在Lambda中已經不需要宣告i為final了。

其次,要明白一個重要的道理:Lambda要求實現的介面中只有一個方法,像上面的Runnable介面就只有一個run方法,如果一個介面中有多於一個方法,則不能寫成Lambda的形式。

最簡單的Lambda表示式可由逗號分隔的引數列表、->符號和語句塊組成,例如:

Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

在上面這個程式碼中的引數e的型別是由編譯器推理得出的,你也可以顯式指定該引數的型別,例如:

Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );

2、Lamda表示式語句塊

如果Lambda表示式需要更復雜的語句塊,則可以使用花括號將該語句塊括起來,類似於Java中的函式體,例如:

    public static void main(String[] args) {
String separator = ",";
Arrays.asList("a","b","c").forEach((String e) -> {
System.out.print(e + separator);
System.out.println(e);
});
}

Lambda表示式可以引用類成員和區域性變數(會將這些變數隱式得轉換成final的),例如下列兩個程式碼塊的效果完全相同:

String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );

final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach(
( String e ) -> System.out.print( e + separator ) );

3、處理返回值

Lambda表示式有返回值,返回值的型別也由編譯器推理得出。如果Lambda表示式中的語句塊只有一行,則可以不用使用return語句,下列兩個程式碼片段效果相同:

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );

4、函式介面

  Lambda的設計者們為了讓現有的功能與Lambda表示式良好相容,考慮了很多方法,於是產生了函式介面這個概念。函式介面指的是隻有一個函式的介面,這樣的介面可以隱式轉換為Lambda表示式。java.lang.Runnablejava.util.concurrent.Callable是函式式介面的最佳例子。在實踐中,函式式介面非常脆弱:只要某個開發者在該介面中新增一個函式,則該介面就不再是函式式介面進而導致編譯失敗。為了克服這種程式碼層面的脆弱性,並顯式說明某個介面是函式式介面,Java 8 提供了一個特殊的註解@FunctionalInterface(Java 庫中的所有相關介面都已經帶有這個註解了),舉個簡單的函式式介面的定義:

@FunctionalInterface
public interface Functional {
void method();
}

不過有一點需要注意,預設方法和靜態方法不會破壞函式式介面的定義,因此如下的程式碼是合法的。

@FunctionalInterface
public interface FunctionalDefaultMethods {
void method(); default void defaultMethod() {
}
}

更多可以參考官方文件

三、介面的預設方法和靜態方法

1、允許介面有預設實現

  Java 8使用兩個新概念擴充套件了介面的含義:預設方法和靜態方法。預設方法使得介面有點類似traits,不過要實現的目標不一樣。預設方法使得開發者可以在 不破壞二進位制相容性的前提下,往現存介面中新增新的方法,即不強制那些實現了該介面的類也同時實現這個新加的方法。

  預設方法和抽象方法之間的區別在於抽象方法需要實現,而預設方法不需要。介面提供的預設方法會被介面的實現類繼承或者覆寫,例子程式碼如下:

public interface Animal {

    default String getName(){
return "animal";
}
}
 private static class DefaultableImpl implements Defaulable {
} private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
}

2、介面靜態方法

  Dog介面使用關鍵字Animal定義了一個預設方法getName()Cat類實現了這個介面,同時預設繼承了這個介面中的預設方法;Dog類也實現了這個介面,但覆寫了該介面的預設方法,並提供了一個不同的實現。

Java 8帶來的另一個有趣的特性是在介面中可以定義靜態方法,例子程式碼如下:

private interface AnimalFactory {
// Interfaces now allow static methods
static Animal create( Supplier<Animal> supplier ) {
return supplier.get();
}
}

下面的程式碼片段整合了預設方法和靜態方法的使用場景:

Animal dog = AnimalFactory.create(Dog::new);
Animal cat = AnimalFactory.create(Cat::new);
System.out.println(dog.getName());
System.out.println(cat.getName());

這段程式碼的輸出結果如下:

dog
animal

  由於JVM上的預設方法的實現在位元組碼層面提供了支援,因此效率非常高。預設方法允許在不打破現有繼承體系的基礎上改進介面。該特性在官方庫中的應用是:給java.util.Collection介面新增新方法,如stream()parallelStream()forEach()removeIf()等等。

  儘管預設方法有這麼多好處,但在實際開發中應該謹慎使用:在複雜的繼承體系中,預設方法可能引起歧義和編譯錯誤。如果你想了解更多細節,可以參考官方文件

四、supplier用法

supplier也是是用來建立物件的,但是不同於傳統的建立物件語法:new,看下面程式碼:

public class TestSupplier {
private int age; TestSupplier(){
System.out.println(age);
}
public static void main(String[] args) {
//建立Supplier容器,宣告為TestSupplier型別,此時並不會呼叫物件的構造方法,即不會建立物件
Supplier<TestSupplier> sup= TestSupplier::new;
System.out.println("--------");
//呼叫get()方法,此時會呼叫物件的構造方法,即獲得到真正物件
sup.get();
//每次get都會呼叫構造方法,即獲取的物件不同
sup.get();
}
}