jdk8系列1、jdk8 Lamda表達式語法、接口的默認方法和靜態方法、supplier用法

1、簡介

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

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

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

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

2、Lambda表達式和函數式接口

  Lambda表達式(也稱爲閉包)是Java 8中最大和最使人期待的語言改變。它容許咱們將函數當成參數傳遞給某個方法,或者把代碼自己看成數據處理:函數式開發者很是熟悉這些概念。閉包

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

  Lambda的設計耗費了不少時間和很大的社區力量,最終找到一種折中的實現方案,能夠實現簡潔而緊湊的語言結構。ide

一、Lamda表達式語法

 

首先不看Lambda自己的寫法,能夠發現,對於i值的訪問,在Lambda中已經不須要聲明i爲final了。函數

 

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

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

 

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

在上面這個代碼中的參數e的類型是由編譯器推理得出的,你也能夠顯式指定該參數的類型,例如:ui

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

二、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 ) );

三、處理返回值

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;
} );

四、函數接口

  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() {            
    }        
}

更多能夠參考官方文檔

3、接口的默認方法和靜態方法

一、容許接口有默認實現

  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";
    }
}

二、接口靜態方法

  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()等等。

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

4、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();
    }
}
相關文章
相關標籤/搜索