JDK8新特性介紹
JDK8新特性:
? 1,Lambda表達式
? 2,新的日期API
? 3,引入Optional
? 4,使用Base64
? 5,接口的默認方法和靜態(tài)方法
? 6,新增方法引用格式
? 7,新增Stream類
? 8,注解相關(guān)的改變
? 9,支持并行(parallel)數(shù)組
? 10,對并發(fā)類(Concurrency)的擴展。
1,Lambda表達式
Lambda 表達式也可稱為閉包,是推動 Java 8 發(fā)布的最重要新特性。Lambda表達式實質(zhì)上是一個匿名方法,Lambda允許把函數(shù)作為一個方法的參數(shù)(函數(shù)作為參數(shù)傳遞進方法中)或者把代碼看成數(shù)據(jù)。使用 Lambda 表達式可以使代碼變的更加簡潔緊湊。在最簡單的形式中,一個Lambda表達式可以由:用逗號分隔的參數(shù)列表、->符號、函數(shù)體三部分組成,在某些情況下lambda的函數(shù)體會更加復(fù)雜,這時可以把函數(shù)體放到在一對花括號中,就像在Java中定義普通函數(shù)一樣,Lambda可以引用類的成員變量與局部變量(如果這些變量不是final的話,它們會被隱含的轉(zhuǎn)為final,這樣效率更高)。Lambda可能會返回一個值,返回值的類型也是由編譯器推測出來的,如果lambda的函數(shù)體只有一行的話,那么沒有必要顯式使用return語句。
如何使現(xiàn)有的函數(shù)比較好的支持Lambda表達式?
方法是:增加函數(shù)式接口的概念。函數(shù)式接口就是接口中必須有且只有一個抽象方法的普通接口,像這樣的接口可以被隱式轉(zhuǎn)換為Lambda表達式,成為函數(shù)式接口。在使用Lambda表達式的地方,方法聲明必須包含一個函數(shù)式接口。任何的函數(shù)式接口都可以使用Lambda表達式替換,例如:ActionListener、Comparator、Runnable等。
函數(shù)式接口是容易出錯的:如果某個人在接口定義中增加了新的方法,這時,這個接口就不再是函數(shù)式接口了,并且編譯也會失敗。為了克服函數(shù)式接口的這種脆弱性并且能夠明確聲明接口作為函數(shù)式接口的意圖,Java 8增加了一種特殊的注解@FunctionalInterface,但是默認方法與靜態(tài)方法并不影響函數(shù)式接口的契約,可以任意使用。
使用lambda表達式替換匿名類,而實現(xiàn)Runnable接口是匿名類的最好示例。通過() -> {}代碼塊替代了整個匿名類。
java 8之前
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Before Java8, too much code for too little to do");
}
}).start();
java 8方式
new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();
Lambda 表達式免去了使用匿名方法的麻煩,并且給予Java簡單但是強大的函數(shù)化的編程能力。
2,新的日期API
Java 8通過發(fā)布新的Date-Time API (JSR 310)來進一步加強對日期與時間的處理。在舊版的 Java 中,日期時間 API 存在諸多問題,比如:
1,線程不安全:java.util.Date是線程不安全的,所有的日志類都是可變的,這是Java日期類最大的問題之一。
2,設(shè)計差:Java的日期/時間類的定義并不一致,在java.util和java.sql的包中都有日期類,此外用于格式化和解析的類在java.text包中定義。java.util.Date同時包含日期和時間,而java.sql.Date僅包含日期,將其納入java.sql包并不合理。另外這兩個類都有相同的名字,這本身就是一個非常糟糕的設(shè)計。
3,時區(qū)處理麻煩:日期類并不提供國際化,沒有時區(qū)支持,因此Java引入了java.util.Calendar和java.util.TimeZone類,但他們同樣存在上述所有的問題。
Java 8 在 java.time 包下提供了很多新的 API。以下為兩個比較重要的 API:
1.Local(本地) ? 簡化了日期時間的處理,沒有時區(qū)的問題。
2.Zoned(時區(qū)) ? 通過制定的時區(qū)處理日期時間。
新的java.time包涵蓋了所有處理日期,時間,日期/時間,時區(qū),時刻(instants),過程(during)與時鐘(clock)的操作。
3,引入Optional
Optional類實際上是個容器:它可以保存類型T的值,或者僅僅保存null。Optional 類的引入很好的解決空指針異常。Optional提供很多有用的方法,這樣我們就不用顯式進行空值檢測。盡量避免在程序中直接調(diào)用Optional對象的get()和isPresent()方法,避免使用Optional類型聲明實體類的屬性。
(1)Optional.of(T t) : 創(chuàng)建一個 Optional 實例
(2)Optional.empty() : 創(chuàng)建一個空的 Optional 實例
(3)Optional.ofNullable(T t):若 t 不為 null,創(chuàng)建 Optional 實例,否則創(chuàng)建空實例
(4)isPresent() : 判斷是否包含值 orElse(T t) : 如果調(diào)用對象包含值,返回該值,否則返回t
(5)orElseGet(Supplier s) :如果調(diào)用對象包含值,返回該值,否則返回 s 獲取的值
(6)map(Function f): 如果有值對其處理,并返回處理后的Optional,否則返回Optional.empty()
(7)flatMap(Function mapper):與 map 類似,要求返回值必須是Optional
1.創(chuàng)建optional對象,一般用ofNullable()而不用of():
(1)empty() :用于創(chuàng)建一個沒有值的Optional對象:Optional<String> emptyOpt = Optional.empty();
(2)of() :使用一個非空的值創(chuàng)建Optional對象:Optional<String> notNullOpt = Optional.of(str);
(3)ofNullable() :接收一個可以為null的值:Optional<String> nullableOpt = Optional.ofNullable(str);
2.判斷Null:
(1)isPresent():如果創(chuàng)建的對象實例為非空值的話,isPresent()返回true,調(diào)用get()方法會返回該對象,如果沒有值,調(diào)用isPresent()方法會返回false,調(diào)用get()方法拋出NullPointerException異常。
3.獲取對象:
(1)get() : 使用get方法,獲取對象
4.使用map提取對象的值
如果我們要獲取User對象中的roleId屬性值,常見的方式是先判斷是否為null然后直接獲取,但使用Optional中提供的map()方法可以以更簡單的方式實現(xiàn)
5.使用orElse方法設(shè)置默認值,Optional類還包含其他方法用于獲取值,這些方法分別為:
(1)orElse():如果有值就返回,否則返回一個給定的值作為默認值;
(2)orElseGet():與orElse()方法作用類似,區(qū)別在于生成默認值的方式不同。該方法接受一個Supplier<? extends T>函數(shù)式接口參數(shù),用于生成默認值;
(3)orElseThrow():與前面介紹的get()方法類似,當(dāng)值為null時調(diào)用這兩個方法都會拋出NullPointerException異常,區(qū)別在于該方法可以指定拋出的異常類型。
6.使用filter()方法過濾
filter() :可用于判斷Optional對象是否滿足給定條件,一般用于條件過濾,在代碼中,如果filter()方法中的Lambda表達式成立,filter()方法會返回當(dāng)前Optional對象值,否則,返回一個值為空的Optional對象。
4,使用Base64
Base64編碼的作用:
由于某些系統(tǒng)中只能使用ASCII字符。Base64就是用來將非ASCII字符的數(shù)據(jù)轉(zhuǎn)換成ASCII字符的一種方法。 Base64其實不是安全領(lǐng)域下的加密解密算法,而是一種編碼,也就是說,它是可以被翻譯回原來的樣子。它并不是一種加密過程。所以base64只能算是一個編碼算法,對數(shù)據(jù)內(nèi)容進行編碼來適合傳輸。雖然base64編碼過后原文也變成不能看到的字符格式,但是這種方式很初級,很簡單。
使用Base64編碼原因 :
1.base64是網(wǎng)絡(luò)上最常見的用于傳輸8bit字節(jié)代碼的編碼方式之一。有時我們需要把二進制數(shù)據(jù)編碼為適合放在URL中的形式。這時采用base64編碼具有不可讀性,即所編碼的數(shù)據(jù)不會被人直接看出。
2.用于在http環(huán)境下傳遞較長的標識信息。
在Java 8中,Base64編碼已經(jīng)成為Java類庫的標準,并內(nèi)置了 Base64 編碼的編碼器和解碼器。Base64工具類提供了一套靜態(tài)方法獲取下面三種BASE64編解碼器:
基本:輸出被映射到一組字符A-Za-z0-9+/,編碼不添加任何行標,輸出的解碼僅支持A-Za-z0-9+/。
URL:輸出映射到一組字符A-Za-z0-9+_,輸出是URL和文件。
MIME:輸出隱射到MIME友好格式。輸出每行不超過76字符,并且使用'\r'并跟隨'\n'作為分割。編碼輸出最后沒有行分割。
5,接口的默認方法和靜態(tài)方法
Java 8用默認方法與靜態(tài)方法這兩個新概念來擴展接口的聲明。默認方法與抽象方法不同之處在于抽象方法必須要求實現(xiàn),但是默認方法則沒有這個要求,就是接口可以有實現(xiàn)方法,而且不需要實現(xiàn)類去實現(xiàn)其方法。我們只需在方法名前面加個default關(guān)鍵字即可實現(xiàn)默認方法。為什么要有這個特性?以前當(dāng)需要修改接口的時候,需要修改全部實現(xiàn)該接口的類。而引進的默認方法的目的是為了解決接口的修改與現(xiàn)有的實現(xiàn)不兼容的問題。
默認方法的代碼格式如下:
public interface MyInterface{
default void print(){
System.out.println("我是接口的默認實現(xiàn)");
}
}
當(dāng)出現(xiàn)這樣的情況,一個類實現(xiàn)了多個接口,且這些接口有相同的默認方法,這種情況的解決方法:
1.是創(chuàng)建自己的默認方法,來覆蓋重寫接口的默認方法
2.可以使用 super 來調(diào)用指定接口的默認方法
Java 8 的另一個特性是接口可以聲明(并且可以提供實現(xiàn))靜態(tài)方法。
在JVM中,默認方法的實現(xiàn)是非常高效的,并且通過字節(jié)碼指令為方法調(diào)用提供了支持。默認方法允許繼續(xù)使用現(xiàn)有的Java接口,而同時能夠保障正常的編譯過程。盡管默認方法非常強大,但是在使用默認方法時我們需要小心注意一個地方:在聲明一個默認方法前,請仔細思考是不是真的有必要使用默認方法,因為默認方法會帶給程序歧義,并且在復(fù)雜的繼承體系中容易產(chǎn)生編譯錯誤。
6,新增方法引用格式
方法引用提供了非常有用的語法,可以直接引用已有Java類或?qū)ο螅▽嵗┑姆椒ɑ驑?gòu)造器。與lambda聯(lián)合使用,方法引用可以使語言的構(gòu)造更緊湊簡潔,減少冗余代碼。
定義了4個方法的Car這個類作為例子,區(qū)分Java中支持的4種不同的方法引用。
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());
}
}
第一種方法引用是構(gòu)造器引用,它的語法是Class::new,或者更一般的Class< T >::new。請注意構(gòu)造器沒有參數(shù)。
final Car car = Car.create(Car::new);
final List< Car > cars = Arrays.asList(car);
第二種方法引用是靜態(tài)方法引用,它的語法是Class::static_method。請注意這個方法接受一個Car類型的參數(shù)
cars.forEach(Car::collide);
第三種方法引用是特定類的任意對象的方法引用,它的語法是Class::method。請注意,這個方法沒有參數(shù)。
cars.forEach(Car::repair);
第四種方法引用是特定對象的方法引用,它的語法是instance::method。請注意,這個方法接受一個Car類型的參數(shù)
final Car police = Car.create(Car::new);
cars.forEach(police::follow);
7,新增Stream類
ava 8 API添加了一個新的抽象稱為流Stream把真正的函數(shù)式編程風(fēng)格引入到Java中,可以讓你以一種聲明的方式處理數(shù)據(jù)。Stream 使用一種類似用 SQL 語句從數(shù)據(jù)庫查詢數(shù)據(jù)的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。Stream API極大簡化了集合框架的處理,這種風(fēng)格將要處理的元素集合看作一種流, 流在管道中傳輸, 并且可以在管道的節(jié)點上進行處理, 比如篩選, 排序,聚合等。
Stream流有一些特性:
1.Stream流不是一種數(shù)據(jù)結(jié)構(gòu),不保存數(shù)據(jù),它只是在原數(shù)據(jù)集上定義了一組操作。
2.這些操作是惰性的,即每當(dāng)訪問到流中的一個元素,才會在此元素上執(zhí)行這一系列操作。
3.Stream不保存數(shù)據(jù),故每個Stream流只能使用一次。
所以這邊有兩個概念:流、管道。元素流在管道中經(jīng)過中間操作的處理,最后由最終操作得到前面處理的結(jié)果。這里有2個操作:中間操作、最終操作。
中間操作:返回結(jié)果都是Stream,故可以多個中間操作疊加。
終止操作:用于返回我們最終需要的數(shù)據(jù),只能有一個終止操作。
使用Stream流,可以清楚地知道我們要對一個數(shù)據(jù)集做何種操作,可讀性強。而且可以很輕松地獲取并行化Stream流,不用自己編寫多線程代碼,可以更加專注于業(yè)務(wù)邏輯。默認情況下,從有序集合、生成器、迭代器產(chǎn)生的流或者通過調(diào)用Stream.sorted產(chǎn)生的流都是有序流,有序流在并行處理時會在處理完成之后恢復(fù)原順序。無限流的存在,側(cè)面說明了流是惰性的,即每當(dāng)用到一個元素時,才會在這個元素上執(zhí)行這一系列操作。
使用Stream的基本步驟:
1.創(chuàng)建Stream
2.轉(zhuǎn)換Stream,每次轉(zhuǎn)換原有Stream對象不改變,返回一個新的Stream對象(可以有多次轉(zhuǎn)換)
3.對Stream進行聚合操作,獲取想要的結(jié)果
一、 流的生成方法
1.Collection接口的stream()或parallelStream()方法
2.靜態(tài)的Stream.of()、Stream.empty()方法
3.Arrays.stream(array, from, to)
4.靜態(tài)的Stream.generate()方法生成無限流,接受一個不包含引元的函數(shù)
5.靜態(tài)的Stream.iterate()方法生成無限流,接受一個種子值以及一個迭代函數(shù)
6.Pattern接口的splitAsStream(input)方法
7.靜態(tài)的Files.lines(path)、Files.lines(path, charSet)方法
8.靜態(tài)的Stream.concat()方法將兩個流連接起來
二、 流的Intermediate方法(中間操作)
1.filter(Predicate) :將結(jié)果為false的元素過濾掉
2.map(fun) :轉(zhuǎn)換元素的值,可以用方法引元或者lambda表達式
3.flatMap(fun) :若元素是流,將流攤平為正常元素,再進行元素轉(zhuǎn)換
4.limit(n) :保留前n個元素
5.skip(n) :跳過前n個元素
6.distinct() :剔除重復(fù)元素
7.sorted() :將Comparable元素的流排序
8.sorted(Comparator) :將流元素按Comparator排序
9.peek(fun) :流不變,但會把每個元素傳入fun執(zhí)行,可以用作調(diào)試
三、 流的Terminal方法(終結(jié)操作)
(1)約簡操作
1.reduce(fun) :從流中計算某個值,接受一個二元函數(shù)作為累積器,從前兩個元素開始持續(xù)應(yīng)用它,累積器的中間結(jié)果作為第一個參數(shù),流元素作為第二個參數(shù)
2.reduce(a, fun) :a為幺元值,作為累積器的起點
3.reduce(a, fun1, fun2) :與二元變形類似,并發(fā)操作中,當(dāng)累積器的第一個參數(shù)與第二個參數(shù)都為流元素類型時,可以對各個中間結(jié)果也應(yīng)用累積器進行合并,但是當(dāng)累積器的第一個參數(shù)不是流元素類型而是類型T的時候,各個中間結(jié)果也為類型T,需要fun2來將各個中間結(jié)果進行合并
(2)收集操作
1.iterator():
2.forEach(fun):
3.forEachOrdered(fun) :可以應(yīng)用在并行流上以保持元素順序
4.toArray():
5.toArray(T[] :: new) :返回正確的元素類型
6.collect(Collector):
7.collect(fun1, fun2, fun3) :fun1轉(zhuǎn)換流元素;fun2為累積器,將fun1的轉(zhuǎn)換結(jié)果累積起來;fun3為組合器,將并行處理過程中累積器的各個結(jié)果組合起來
(3)查找與收集操作
1.max(Comparator):返回流中最大值
2.min(Comparator):返回流中最小值
3.count():返回流中元素個數(shù)
4.findFirst() :返回第一個元素
5.findAny() :返回任意元素
6.anyMatch(Predicate) :任意元素匹配時返回true
7.allMatch(Predicate) :所有元素匹配時返回true
8.noneMatch(Predicate) :沒有元素匹配時返回true
8,注解相關(guān)的改變
(1)可以進行重復(fù)注解
自從Java 5引入了注解機制,這一特性就變得非常流行并且廣為使用。然而,使用注解的一個限制是相同的注解在同一位置只能聲明一次,不能聲明多次。Java 8打破了這條規(guī)則,引入了重復(fù)注解機制,這樣相同的注解可以在同一地方聲明多次。
重復(fù)注解機制本身必須用@Repeatable注解。事實上,這并不是語言層面上的改變,更多的是編譯器的技巧,底層的原理保持不變。
(2)擴展注解的支持
Java 8擴展了注解的上下文?,F(xiàn)在幾乎可以為任何東西添加注解:局部變量、泛型類、父類與接口的實現(xiàn),就連方法的異常也能添加注解。
9,支持并行(parallel)數(shù)組
Java 8增加了大量的新方法來對數(shù)組進行并行處理。可以說,最重要的是parallelSort()方法,因為它可以在多核機器上極大提高數(shù)組排序的速度。下面的例子展示了新方法(parallelXxx)的使用。
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
public class ParallelArrays {
public static void main( String[] args ) {
long[] arrayOfLong = new long [ 20000 ];
Arrays.parallelSetAll( arrayOfLong,
index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
i -> System.out.print( i + " " ) );
System.out.println();
Arrays.parallelSort( arrayOfLong );
Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
i -> System.out.print( i + " " ) );
System.out.println();
}
}
上面的代碼片段使用了parallelSetAll()方法來對一個有20000個元素的數(shù)組進行隨機賦值。然后,調(diào)用parallelSort方法。這個程序首先打印出前10個元素的值,之后對整個數(shù)組排序。這個程序在控制臺上的輸出如下(請注意數(shù)組元素是隨機生產(chǎn)的)。
10,對并發(fā)類(Concurrency)的擴展。
在新增Stream機制與lambda的基礎(chǔ)之上,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法來支持聚集操作。同時也在java.util.concurrent.ForkJoinPool類中加入了一些新方法來支持共有資源池(common pool)。
新增的java.util.concurrent.locks.StampedLock類提供一直基于容量的鎖,這種鎖有三個模型來控制讀寫操作(它被認為是不太有名的java.util.concurrent.locks.ReadWriteLock類的替代者)。
在java.util.concurrent.atomic包中還增加了下面這些類:
1.DoubleAccumulator:
2.DoubleAdder
3.LongAccumulator
4.LongAdder