說明
jdk8雖然出現(xiàn)很久了,但是可能我們還是有很多人并不太熟悉,本文主要就是介紹說明一些jdk8相關(guān)的內(nèi)容。
主要會講解:
* lambda表達式
* 方法引用
* 默認方法
* Stream
* 用Optional取代null
* 新的日志和時間
* CompletableFuture
* 去除了永久代(PermGen) 被元空間(Metaspace)代替
我們來看看阿里規(guī)范里面涉及到j(luò)dk8相關(guān)內(nèi)容:
jdk8開篇
https://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html
主要有:
1:lambda表達式:一種新的語言特性,能夠把函數(shù)作為方法的參數(shù)或?qū)⒋a作為數(shù)據(jù)。lambda表達式使你在表示函數(shù)接口(具有單個方法的接口)的實例更加緊湊。
2:方法引用 是lambda表達式的一個簡化寫法,所引用的方法其實是lambda表達式的方法體實現(xiàn),這樣使代碼更容易閱讀
3:默認方法:Java 8引入default method,或者叫virtual extension
method,目的是為了讓接口可以事后添加新方法而無需強迫所有實現(xiàn)該接口的類都提供新方法的實現(xiàn)。也就是說它的主要使用場景可能會涉及代碼演進。
4: Stream 不是 集合元素,也不是數(shù)據(jù)結(jié)構(gòu),它相當于一個 高級版本的
Iterator,不可以重復遍歷里面的數(shù)據(jù),像水一樣,流過了就一去不復返。它和普通的 Iterator 不同的是,它可以并行遍歷,普通的 Iterator
只能是串行,在一個線程中執(zhí)行。操作包括:中間操作 和 最終操作(只能操作一遍) 串行流操作在一個線程中依次完成。并行流在多個線程中完成,主要利用了 JDK7
的 Fork/Join 框架來拆分任務(wù)和加速處理。相比串行流,并行流可以很大程度提高程序的效率
5:用Optional取代null
6:新的日志和時間,可以使用Instant代替Date LocalDateTime代替Calendar
DateTimeFormatter代替SimpleDateFormat
7:CompletableFuture:CompletableFuture提供了非常強大的Future的擴展功能,可以幫助我們簡化異步編程的復雜性,并且提供了函數(shù)式編程的能力,可以通過回調(diào)的方式處理計算結(jié)果,也提供了轉(zhuǎn)換和組合
CompletableFuture 的方法。
8:去除了永久代(PermGen) 被元空間(Metaspace)代替 配置:-XX:MetaspaceSize=8m
-XX:MaxMetaspaceSize=80m 代替 -XX:PermSize=10m -XX:MaxPermSize=10m
lambda
JDK8最大的特性應(yīng)該非lambda莫屬!
IDEA工具自動提示:
lambda語法結(jié)構(gòu) :
完整的Lambda表達式由三部分組成:參數(shù)列表、箭頭、聲明語句;
(Type1 param1, Type2 param2, ..., TypeN paramN) -> { statment1; statment2;
//............. return statmentM;}
絕大多數(shù)情況,編譯器都可以從上下文環(huán)境中推斷出lambda表達式的參數(shù)類型,所以參數(shù)可以省略:
(param1,param2, ..., paramN) -> { statment1; statment2; //............. return
statmentM;}
當lambda表達式的參數(shù)個數(shù)只有一個,可以省略小括號:
param1 -> { statment1; statment2; //............. return statmentM;}
當lambda表達式只包含一條語句時,可以省略大括號、return和語句結(jié)尾的分號:
param1 -> statment
在那里以及如何使用Lambda????
你可以在函數(shù)式接口上面使用Lambda表達式。
備注: JDK定義了很多現(xiàn)在的函數(shù)接口,實際自己也可以定義接口去做為表達式的返回,只是大多數(shù)情況下JDK定義的直接拿來就可以用了。
Java SE 7中已經(jīng)存在的函數(shù)式接口:
* java.lang.Runnable
<http://download.oracle.com/javase/7/docs/api/java/lang/Runnable.html>
* java.util.concurrent.Callable
<http://download.oracle.com/javase/7/docs/api/java/util/concurrent/Callable.html>
* java.security.PrivilegedAction
<http://download.oracle.com/javase/7/docs/api/java/security/PrivilegedAction.html>
* java.util.Comparator
<http://download.oracle.com/javase/7/docs/api/java/util/Comparator.html>
* java.util.concurrent.Callable
<https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Callable.html>
* java.io.FileFilter
<http://download.oracle.com/javase/7/docs/api/java/io/FileFilter.html>
* java.beans.PropertyChangeListener
<http://www.fxfrog.com/docs_www/api/java/beans/PropertyChangeListener.html>
除此之外,Java SE 8中增加了一個新的包:java.util.function,它里面包含了常用的函數(shù)式接口,例如:
* Predicate<T>——接收T對象并返回boolean
* Consumer<T>——接收T對象,不返回值
* Function<T, R>——接收T對象,返回R對象
* Supplier<T>——提供T對象(例如工廠),不接收值
隨便看幾個:
默認方法
Java 8 引入了新的語言特性——默認方法(Default Methods)。
Default methods
<http://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html> enable
new functionality to be added to the interfaces of libraries and ensure binary
compatibility with code written for older versions of those interfaces.
默認方法允許您添加新的功能到現(xiàn)有庫的接口中,并能確保與采用舊版本接口編寫的代碼的二進制兼容性。
默認方法是在接口中的方法簽名前加上了 default 關(guān)鍵字的實現(xiàn)方法。
為什么要有默認方法
在 java 8 之前,接口與其實現(xiàn)類之間的 耦合度 太高了(tightly
coupled),當需要為一個接口添加方法時,所有的實現(xiàn)類都必須隨之修改。默認方法解決了這個問題,它可以為接口添加新的方法,而不會破壞已有的接口的實現(xiàn)。這在
lambda 表達式作為 java 8 語言的重要特性而出現(xiàn)之際,為升級舊接口且保持向后兼容(backward compatibility)提供了途徑。
這個?forEach?方法是 jdk 1.8 新增的接口默認方法,正是因為有了默認方法的引入,才不會因為?Iterable?接口中添加了?forEach
?方法就需要修改所有?Iterable?接口的實現(xiàn)類。
方法引用(Method references)
如果一個Lambda表達式僅僅是調(diào)用方法的情況,那么就可以用方法引用來完成,這種情況下使用方法引用代碼更易讀。
方法引用語法:
目標引用放在分隔符::前,方法的名稱放在后面。
names2.forEach(System.out::println);//1
names2.forEach(s->System.out.println(s));//2
第二行代碼的lambda表達式僅僅就是調(diào)用方法,調(diào)用的System.out的println方法,所以可以用方法引用寫成System.out::println即可。
方法引用的種類(Kinds of method references)
方法引用有很多種,它們的語法如下:
* 靜態(tài)方法引用:ClassName::methodName
* 實例上的實例方法引用:instanceReference::methodName
* 父類的實例方法引用:super::methodName
*
類型上的實例方法引用:ClassName::methodName
備注:String::toString 等價于lambda表達式 (s) -> s.toString()
這里不太容易理解,實例方法要通過對象來調(diào)用,方法引用對應(yīng)Lambda,Lambda的第一個參數(shù)會成為調(diào)用實例方法的對象。
* 構(gòu)造方法引用:Class::new
*
數(shù)組構(gòu)造方法引用:TypeName[]::new
個人理解:方法引用,說白了,用更好,不用也可以,如果可以盡量用?。?!
Stream
Java 8 中的 Stream 是對集合(Collection)對象功能的增強,它專注于對集合對象進行各種非常便利、高效的聚合操作(aggregate
operation),或者大批量數(shù)據(jù)操作 (bulk data operation)。Stream API 借助于同樣新出現(xiàn)的Lambda 表達式
,極大的提高編程效率和程序可讀性。同時它提供串行和并行兩種模式進行匯聚操作,并發(fā)模式能夠充分利用多核處理器的優(yōu)勢,使用 fork/join
并行方式來拆分任務(wù)和加速處理過程。通常編寫并行代碼很難而且容易出錯, 但使用 Stream API
無需編寫一行多線程的代碼,就可以很方便地寫出高性能的并發(fā)程序。
* Stream 不是集合元素,它不是數(shù)據(jù)結(jié)構(gòu)并不保存數(shù)據(jù),它是有關(guān)算法和計算的,它更像一個高級版本的 Iterator。
* Stream 就如同一個迭代器(Iterator),單向,不可往復,數(shù)據(jù)只能遍歷一次,遍歷過一次后即用盡了,就好比流水從面前流過,一去不復返。
* 和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。
對stream的操作分為三類。
* 創(chuàng)建stream
* 中間操作(intermediate operations)【沒有終止操作是不會執(zhí)行的】
* 終止操作(terminal operations):
中間操作會返回另一個流。可以用鏈式編程.的形式繼續(xù)調(diào)用。在沒有終止操作的時候,中間操作是不會執(zhí)行的。
終止操作不會返回流了,而是返回結(jié)果(比如返回void-僅僅System.out輸出,比如返回總數(shù) int,返回一個集合list等等)
例如:
流的創(chuàng)建
3種方式創(chuàng)建流,普通流調(diào)用
* 通過Stream接口的靜態(tài)工廠方法
*
通過Arrays方法
*
通過Collection接口的默認方法
//通過Stream接口的靜態(tài)工廠方法 Stream stream = Stream.of("hello", "world", "hello
world"); String[] strArray = new String[]{"hello", "world", "hello world"};
//通過Stream接口的靜態(tài)工廠方法 Stream stream1 = Stream.of(strArray); //通過Arrays方法 Stream
stream2 = Arrays.stream(strArray); List<String> list = Arrays.asList(strArray);
//通過Collection接口的默認方法 Stream stream3 = list.stream();
本質(zhì)都是StreamSupport.stream。
通過Collection接口的默認方法獲取并行流。
或者通過stream流調(diào)用parallel獲取并行流
只需要對并行流調(diào)用sequential方法就可以把它變成順序流
中間操作
終止操作
并行流
可以通過對收集源調(diào)用parallelStream方法來把集合轉(zhuǎn)換為并行流。并行流就是一個把內(nèi)容分成多個數(shù)據(jù)
塊,并用不同的線程分別處理每個數(shù)據(jù)塊的流。這樣一來,你就可以自動把給定操作的工作負荷分配給多核處理器的所有內(nèi)核,讓它們都忙起來。
并行流用的線程是從哪兒來的?有多少個?怎么自定義這個過程呢?
并行流內(nèi)部使用了默認的ForkJoinPool,它默認的線程數(shù)量就是你的處理器數(shù)量,這個值是由
Runtime.getRuntime().availableProcessors()得到的。但是你可以通過系統(tǒng)屬性
java.util.concurrent.ForkJoinPool.common. parallelism來改變線程池大小,如下所示:
System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");
這是一個全局設(shè)置,因此它將影響代碼中所有的并行流。反過來說,目前還無法專為某個
并行流指定這個值。一般而言,讓ForkJoinPool的大小等于處理器數(shù)量是個不錯的默認值,
除非你有很好的理由,否則我們強烈建議你不要修改它
測試并行流和順序流速度
//Sequential Sort, 采用順序流進行排序 @Test public void sequentialSort(){ long t0 =
System.nanoTime(); long count = values.stream().sorted().count();
System.err.println("count = " + count); long t1 = System.nanoTime(); long
millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));
//sequential sort took: 1932 ms } //parallel Sort, 采用并行流進行排序 @Test public void
parallelSort(){ long t0 = System.nanoTime(); long count =
values.parallelStream().sorted().count(); System.err.println("count = " +
count); long t1 = System.nanoTime(); long millis =
TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));
//parallel sort took: 1373 ms 并行排序所花費的時間大約是順序排序的一半。 }
錯誤使用流
class Accumlator{ public long total = 0; public void add(long value) { total
+= value; } } public class ParallelTest { public static void main(String[]
args) { //錯誤使用并行流示例 System.out.println("SideEffect parallel sum done in :" +
measureSumPerf(ParallelTest::sideEffectParallelSum, 1_000_000_0) + "mesecs");
System.out.println("================="); //正確應(yīng)該這樣的
System.out.println("SideEffect sum done in :" +
measureSumPerf(ParallelTest::sideEffectSum, 1_000_000_0) + "mesecs"); }
//錯誤使用并行流 public static long sideEffectParallelSum(long n) { Accumlator
accumlator = new Accumlator(); LongStream.rangeClosed(1,
n).parallel().forEach(accumlator::add); return accumlator.total; } //正確使用流
public static long sideEffectSum(long n) { Accumlator accumlator = new
Accumlator(); LongStream.rangeClosed(1, n).forEach(accumlator::add); return
accumlator.total; } //定義測試函數(shù) public static long measureSumPerf(Function<Long,
Long> adder, long n) { long fastest = Long.MAX_VALUE; //迭代10次 for (int i = 0; i
< 2; i++) { long start=System.nanoTime(); long sum = adder.apply(n); long
duration=(System.nanoTime()-start)/1_000_000; System.out.println("Result: " +
sum); //取最小值 if (duration < fastest) { fastest = duration; } } return fastest;
} }
本質(zhì)問題在于total += value;它不是原子操作,并行調(diào)用的時候它會改變多個線程共享的對象的可變狀態(tài),從而導致錯誤,在使用并行流需要避免這類問題發(fā)生!
思考: 什么情況結(jié)果正常,但是并行流比順序流慢的情況呢???
并行流中更新共享變量,如果你加入了同步,很可能會發(fā)現(xiàn)線程競爭抵消了并行帶來的性能提升!
特別是limit和findFirst等依賴于元素順序的操作,它們在并行流上執(zhí)行的代價非常大
對于較小的數(shù)據(jù)量,選擇并行流幾乎從來都不是一個好的決定。并行處理少數(shù)幾個元素的好處還抵不上并行化造成的額外開銷。
備注:
sort或distinct等操作接受一個流,再生成一個流(中間操作),從流中排序和刪除重復項時都需要知道所有集合數(shù)據(jù),如果集合數(shù)據(jù)很大可能會有問題(如果數(shù)據(jù)大,都放內(nèi)存,內(nèi)存不夠就會OOM了)。
使用并行流還是順序流都應(yīng)該應(yīng)該測試,以及壓測,如果在并行流正常的情況下,效率有提升就選擇并行流,如果順序流快就選擇順序流。
CompletableFuture異步函數(shù)式編程
引入CompletableFuture原因
Future模式的缺點
* Future雖然可以實現(xiàn)獲取異步執(zhí)行結(jié)果的需求,但是它沒有提供通知的機制,我們無法得知Future什么時候完成。
* 要么使用阻塞,在future.get()的地方等待future返回的結(jié)果,這時又變成同步操作
。要么使用isDone()輪詢地判斷Future是否完成,這樣會耗費CPU的資源。
Future 接口的局限性
future接口可以構(gòu)建異步應(yīng)用,但依然有其局限性。它很難直接表述多個Future 結(jié)果之間的依賴性。實際開發(fā)中,我們經(jīng)常需要達成以下目的:
* 將兩個異步計算合并為一個——這兩個異步計算之間相互獨立,同時第二個又依賴于第
一個的結(jié)果。
* 等待 Future 集合中的所有任務(wù)都完成。
* 僅等待 Future 集合中最快結(jié)束的任務(wù)完成(有可能因為它們試圖通過不同的方式計算同
一個值),并返回它的結(jié)果。
* 通過編程方式完成一個 Future 任務(wù)的執(zhí)行(即以手工設(shè)定異步操作結(jié)果的方式)。
* 應(yīng)對 Future 的完成事件(即當 Future 的完成事件發(fā)生時會收到通知,并能使用 Future
計算的結(jié)果進行下一步的操作,不只是簡單地阻塞等待操作的結(jié)果)
新的CompletableFuture將使得這些成為可能。
CompletableFuture提供了四個靜態(tài)方法用來創(chuàng)建CompletableFuture對象:
方法入?yún)⒑头祷刂涤兴鶇^(qū)別。
里面有非常多的方法,返回為CompletableFuture之后可以用鏈式編程.的形式繼續(xù)調(diào)用,最后調(diào)用一個不是返回CompletableFuture的介紹,和流式操作里面的中間操作-終止操作。
日期
/** * 可以使用Instant代替Date * LocalDateTime代替Calendar *
DateTimeFormatter代替SimpleDateFormat */ public static void main(String args[]) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd
HH:mm:ss"); LocalDateTime now = LocalDateTime.now();
System.out.println(now.format(formatter)); //10分鐘前 String d1 =
now.minusMinutes(10).format(formatter); //10分鐘后 String d2 =
now.plusMinutes(10).format(formatter); System.out.println(d1);
System.out.println(d2); LocalDateTime t5 = LocalDateTime.parse("2019-01-01
00:00:00", formatter); System.out.println(t5.format(formatter)); }
JVM方面改變
去除了永久代(PermGen) 被元空間(Metaspace)代替 配置:-XX:MetaspaceSize=8m
-XX:MaxMetaspaceSize=80m 代替 -XX:PermSize=10m -XX:MaxPermSize=10m
用Optional取代null
Optional對象創(chuàng)建
1、 創(chuàng)建空對象
Optional<String> optStr = Optional.empty();
上面的示例代碼調(diào)用empty()方法創(chuàng)建了一個空的Optional對象型。
2、創(chuàng)建對象:不允許為空
Optional提供了方法of()用于創(chuàng)建非空對象,該方法要求傳入的參數(shù)不能為空,否則拋NullPointException,示例如下:
Optional<String> optStr = Optional.of(str); //
當str為null的時候,將拋出NullPointException
3、創(chuàng)建對象:允許為空
如果不能確定傳入的參數(shù)是否存在null值的可能性,則可以用Optional的ofNullable()方法創(chuàng)建對象,如果入?yún)閚ull,則創(chuàng)建一個空對象。示例如下:
Optional<String> optStr = Optional.ofNullable(str); // 如果str是null,則創(chuàng)建一個空對象
常用方法
String str = null; len =
Optional.ofNullable(str).map(String::length).orElse(0);
//不會報NullPointerException
如果讀完覺得有收獲的話,歡迎點贊、關(guān)注、加公眾號 [匠心零度] ,查閱更多精彩歷史?。?!
熱門工具 換一換