北京java培訓:JAVA8的10大新特性
本文是北京java培訓小編搜索整理的關於詳解JAVA8的10大新特性,有需要的朋友們一起看看吧!
一、介面的默認方法
Java8允許我們給介面添加一個非抽象的方法實現,只需要使用default關鍵字即可,這個特徵又叫做擴展方法,示例如下:
代碼如下:
interfaceFormula{
doublecalculate(inta);
defaultdoublesqrt(inta){
returnMath.sqrt(a);
}
}
Formula介面在擁有calculate方法之外同時還定義了sqrt方法,實現了Formula介面的子類只需要實現一個calculate方法,默認方法sqrt將在子類上可以直接使用。
代碼如下:
Formulaformula=newFormula{
@Override
publicdoublecalculate(inta){
returnsqrt(a*100);
}
};
formula.calculate(100);//100.0
formula.sqrt(16);//4.0
文中的formula被實現為一個匿名類的實例,該代碼非常容易理解,6行代碼實現了計算sqrt(a*100)。在下一節中,我們將會看到實現單方法介面的更簡單的做法。
譯者註:在Java中只有單繼承,如果要讓一個類賦予新的特性,通常是使用介面來實現,在C++中支持多繼承,允許一個子類同時具有多個父類的介面與功能,在其他語言中,讓一個類同時具有其他的可復用代碼的方法叫做mixin。新的Java8的這個特新在編譯器實現的角度上來說更加接近Scala的trait。在C#中也有名為擴展方法的概念,允許給已存在的類型擴展方法,和Java8的這個在語義上有差別。
二、Lambda表達式
首先看看在老版本的Java中是如何排列字元串的:
代碼如下:
List<String>names=Arrays.asList("peter","anna","mike","xenia");
Collections.sort(names,newComparator<String>{
@Override
publicintcompare(Stringa,Stringb){
returnb.compareTo(a);
}
});
只需要給靜態方法Collections.sort傳入一個List對象以及一個比較器來按指定順序排列。通常做法都是創建一個匿名的比較器對象然後將其傳遞給sort方法。
在Java8中你就沒必要使用這種傳統的匿名對象的方式了,Java8提供了更簡潔的語法,lambda表達式:
代碼如下:
Collections.sort(names,(Stringa,Stringb)->{
returnb.compareTo(a);
});
看到了吧,代碼變得更段且更具有可讀性,但是實際上還可以寫得更短:
代碼如下:
Collections.sort(names,(Stringa,Stringb)->b.compareTo(a));
對於函數體只有一行代碼的,你可以去掉大括弧{}以及return關鍵字,但是你還可以寫得更短點:
代碼如下:
Collections.sort(names,(a,b)->b.compareTo(a));
Java編譯器可以自動推導出參數類型,所以你可以不用再寫一次類型。接下來我們看看lambda表達式還能作出什麼更方便的東西來:
三、函數式介面
Lambda表達式是如何在java的類型系統中表示的呢?每一個lambda表達式都對應一個類型,通常是介面類型。而「函數式介面」是指僅僅只包含一個抽象方法的介面,每一個該類型的lambda表達式都會被匹配到這個抽象方法。因為默認方法不算抽象方法,所以你也可以給你的函數式介面添加默認方法。
我們可以將lambda表達式當作任意只包含一個抽象方法的介面類型,確保你的介面一定達到這個要求,你只需要給你的介面添加@FunctionalInterface註解,編譯器如果發現你標註了這個註解的介面有多於一個抽象方法的時候會報錯的。
示例如下:
代碼如下:
@FunctionalInterface
interfaceConverter<F,T>{
Tconvert(Ffrom);
}
Converter<String,Integer>converter=(from)->Integer.valueOf(from);
Integerconverted=converter.convert("123");
System.out.println(converted);//123
需要注意如果@FunctionalInterface如果沒有指定,上面的代碼也是對的。
譯者注將lambda表達式映射到一個單方法的介面上,這種做法在Java8之前就有別的語言實現,比如RhinoJavaScript解釋器,如果一個函數參數接收一個單方法的介面而你傳遞的是一個function,Rhino解釋器會自動做一個單介面的實例到function的適配器,典型的應用場景有org.w3c.dom.events.EventTarget的addEventListener第二個參數EventListener。
四、方法與構造函數引用
前一節中的代碼還可以通過靜態方法引用來表示:
代碼如下:
Converter<String,Integer>converter=Integer::valueOf;
Integerconverted=converter.convert("123");
System.out.println(converted);//123
Java8允許你使用::關鍵字來傳遞方法或者構造函數引用,上面的代碼展示了如何引用一個靜態方法,我們也可以引用一個對象的方法:
代碼如下:
converter=something::startsWith;
Stringconverted=converter.convert("Java");
System.out.println(converted);//"J"
接下來看看構造函數是如何使用::關鍵字來引用的,首先我們定義一個包含多個構造函數的簡單類:
代碼如下:
classPerson{
StringfirstName;
StringlastName;
Person{}
Person(StringfirstName,StringlastName){
this.firstName=firstName;
this.lastName=lastName;
}
}
接下來我們指定一個用來創建Person對象的對象工廠介面:
代碼如下:
interfacePersonFactory<PextendsPerson>{
Pcreate(StringfirstName,StringlastName);
}
這裡我們使用構造函數引用來將他們關聯起來,而不是實現一個完整的工廠:
代碼如下:
PersonFactory<Person>personFactory=Person::new;
Personperson=personFactory.create("Peter","Parker");
我們只需要使用Person::new來獲取Person類構造函數的引用,Java編譯器會自動根據PersonFactory.create方法的簽名來選擇合適的構造函數。
五、Lambda作用域
在lambda表達式中訪問外層作用域和老版本的匿名對象中的方式很相似。你可以直接訪問標記了final的外層局部變數,或者實例的欄位以及靜態變數。
六、訪問局部變數
我們可以直接在lambda表達式中訪問外層的局部變數:
代碼如下:
finalintnum=1;
Converter<Integer,String>stringConverter=
(from)->String.valueOf(from+num);
stringConverter.convert(2);//3
但是和匿名對象不同的是,這裡的變數num可以不用聲明為final,該代碼同樣正確:
代碼如下:
intnum=1;
Converter<Integer,String>stringConverter=
(from)->String.valueOf(from+num);
stringConverter.convert(2);//3
不過這裡的num必須不可被後面的代碼修改(即隱性的具有final的語義),例如下面的就無法編譯:
代碼如下:
intnum=1;
Converter<Integer,String>stringConverter=
(from)->String.valueOf(from+num);
num=3;
在lambda表達式中試圖修改num同樣是不允許的。
七、訪問對象欄位與靜態變數
和本地變數不同的是,lambda內部對於實例的欄位以及靜態變數是即可讀又可寫。該行為和匿名對象是一致的:
代碼如下:classLambda4{
staticintouterStaticNum;
intouterNum;
voidtestScopes{
Converter<Integer,String>stringConverter1=(from)->{
outerNum=23;
returnString.valueOf(from);
};
Converter<Integer,String>stringConverter2=(from)->{
outerStaticNum=72;
returnString.valueOf(from);
};
}
}
八、訪問介面的默認方法
還記得第一節中的formula例子么,介面Formula定義了一個默認方法sqrt可以直接被formula的實例包括匿名對象訪問到,但是在lambda表達式中這個是不行的。
Lambda表達式中是無法訪問到默認方法的,以下代碼將無法編譯:
代碼如下:
Formulaformula=(a)->sqrt(a*100);
Built-inFunctionalInterfaces
JDK1.8API包含了很多內建的函數式介面,在老Java中常用到的比如Comparator或者Runnable介面,這些介面都增加了@FunctionalInterface註解以便能用在lambda上。