Java8新特性-語言新特性
它允許我們將函數當成參數傳遞給某個方法,或者把代碼本身當作數據處理:函數式開發者非常熟悉這些概念。很多JVM平臺上的語言(Groovy、Scala等)從誕生之日就支持Lambda表達式,但是Java開發者沒有選擇,只能使用匿名內部類代替Lambda表達式。
最簡單的Lambda表達式可由逗號分隔的參數列表、->符號和語句塊組成。
1.1.表達式語法:
Lambda表達式用於表示一個函數,所以它和函數一樣,也擁有參數、返回值、函數體,但它沒有函數名,所以Lambda表達式相當於一個匿名函數。語法如下:
註1:在上面這個代碼中的參數的類型是顯式指定的,當然也可以由編譯器推理得出,例如:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
註2:如果Lambda表達式需要更復雜的語句塊,則可以使用花括號將該語句塊括起來,類似於Java中的函數體,例如:
Arrays.asList( "a", "b", "d" ).forEach( e -> { System.out.print( e ); System.out.print( e ); } );
註3: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 ) );
1.2.lambda表達式使用
以篩選年齡大於10的Person對象為例:
1.2.1.為Lambda表達式定義函數式接口
@FunctionalInterface interface FilterProcessor<T>{ boolean process(T t); }
Lambda的設計者們為了讓現有的功能與Lambda表達式良好兼容,考慮了很多方法,於是產生了函數接口這個概念。函數接口指的是只有一個函數的接口,這樣的接口可以隱式轉換為Lambda表達式。java.lang.Runnable和java.util.concurrent.Callable是函數式接口的最佳例子。在實踐中,函數式接口非常脆弱:只要某個開發者在該接口中添加一個函數,則該接口就不再是函數式接口進而導致編譯失敗。為了克服這種代碼層面的脆弱性,並顯式說明某個接口是函數式接口,Java 8 提供了一個特殊的註解@FunctionalInterface(Java 庫中的所有相關接口都已經帶有這個註解了),舉個簡單的函數式接口的定義:
在為Lambda表達式定義函數式接口時,需要加上註解@FunctionalInterface,該接口只能有一個抽象函數。當該接口中抽象函數個數不是1時就會報錯提示。
不過有一點需要註意,默認方法和靜態方法(下面會說到)不會破壞函數式接口的定義
1.2.2.實現篩選函數
List<T> filter(List<T> list, FilterProcessor<T> filterProcessor){ List<T> result = new ArrayList<T>(); if(filterProcessor.process(t)) result.add(t); return result; }
filter函數接收一個函數式接口,該參數用於接收一個Lambda表達式。
1.2.3.傳遞Lambda表達式
List<Person> result = filter(list, (Person p)->p.getAge()>10);
直接將Lambda表達式作為參數傳遞給filter的函數式接口即可,從而在result中就能獲取年齡超過10歲的Person對象。
二、接口的默認方法和靜態方法
Java 8使用兩個新概念擴展了接口的含義:默認方法和靜態方法。
1.默認方法使得接口有點類似traits,不過要實現的目標不一樣。默認方法使得開發者可以在 不破壞二進制兼容性的前提下,往現存接口中添加新的方法,即不強制那些實現了該接口的類也同時實現這個新加的方法。
默認方法和抽象方法之間的區別在於抽象方法需要實現,而默認方法不需要。接口提供的默認方法會被接口的實現類繼承或者覆寫
2.Java 8帶來的另一個有趣的特性是在接口中可以定義靜態方法,例子代碼如下:
private interface DefaulableFactory { // Interfaces now allow static methods static Defaulable create( Supplier< Defaulable > supplier ) { return supplier.get(); } }
由於JVM上的默認方法的實現在字節碼層面提供了支持,因此效率非常高。默認方法允許在不打破現有繼承體系的基礎上改進接口。該特性在官方庫中的應用是:給java.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等。
盡管默認方法有這麽多好處,但在實際開發中應該謹慎使用:在復雜的繼承體系中,默認方法可能引起歧義和編譯錯誤。
三、方法引用
方法引用使得開發者可以直接引用現存的方法、Java類的構造方法或者實例對象。方法引用和Lambda表達式配合使用,使得java類的構造方法看起來緊湊而簡潔,沒有很多復雜的模板代碼。
下面的例子中,Car類是不同方法引用的例子,可以幫助讀者區分四種類型的方法引用。
public static class Car { public static Car create( final Supplier< Car > supplier ) { return supplier.get(); } public static void collide( final Car car ) { System.out.println( "Collided " + car.toString() ); } public void follow( final Car another ) { System.out.println( "Following the " + another.toString() ); } public void repair() { System.out.println( "Repaired " + this.toString() ); } }
第一種方法引用的類型是構造器引用,語法是Class::new,或者更一般的形式:Class<T>::new。註意:這個構造器沒有參數。
final Car car = Car.create( Car::new ); final List< Car > cars = Arrays.asList( car );
第二種方法引用的類型是靜態方法引用,語法是Class::static_method。註意:這個方法接受一個Car類型的參數。
cars.forEach( Car::collide );
第三種方法引用的類型是某個類的成員方法的引用,語法是Class::method,註意,這個方法沒有定義入參:
cars.forEach( Car::repair );
第四種方法引用的類型是某個實例對象的成員方法的引用,語法是instance::method。註意:這個方法接受一個Car類型的參數:
final Car police = Car.create( Car::new ); cars.forEach( police::follow );
四、重復註解
自從Java 5中引入註解以來,這個特性開始變得非常流行,並在各個框架和項目中被廣泛使用。不過,註解有一個很大的限制是:在同一個地方不能多次使用同一個註解。Java 8打破了這個限制,引入了重復註解的概念,允許在同一個地方多次使用同一個註解。
在Java 8中使用@Repeatable註解定義重復註解,實際上,這並不是語言層面的改進,而是編譯器做的一個trick,底層的技術仍然相同。
五、更好的類型推斷
Java 8編譯器在類型推斷方面有很大的提升,在很多場景下編譯器可以推導出某個參數的數據類型,從而使得代碼更為簡潔。
六、拓寬註解的應用場景
Java 8拓寬了註解的應用場景。現在,註解幾乎可以使用在任何元素上:局部變量、接口類型、超類和接口實現類,甚至可以用在函數的異常定義上。
Java8新特性-語言新特性