JDK1.8新特性中最值得注意的當(dāng)然就是Lambda表達(dá)式了,還有新增了很多關(guān)于Lambda表達(dá)式的新特性,比如函數(shù)式接口、方法引用和構(gòu)造器調(diào)用。處理之外還有Stream API、接口中的默認(rèn)方法和靜態(tài)方法、新的時間日期API,HashMap以及ConcurrentHashMap結(jié)構(gòu)的更改等等。
1、Lambda表達(dá)式
(1)什么是Lambda表達(dá)式呢?Lambda表達(dá)式本質(zhì)上就是一段匿名內(nèi)部類,也可以是一段可以傳遞的代碼。
語法如下:

(2)Lambda表達(dá)式到底方便在哪里?舉例說明

我們發(fā)現(xiàn)實際上這些過濾方法的核心就只有if語句中的條件判斷,其他均為模版代碼,每次變更一下需求,都需要新增一個方法,然后復(fù)制黏貼,假設(shè)這個過濾方法有幾百行,那么這樣的做法難免笨拙了一點。如何進(jìn)行優(yōu)化呢?
a.使用設(shè)計模式

看起來好像代碼更多了一點,不過這是在只有兩個例子的情況下,如果有100個過濾那么會發(fā)現(xiàn)還是設(shè)計模式比較簡潔一點,而且設(shè)計模式好在的地方是對代碼進(jìn)行了整合,看起來邏輯更簡潔明了。不過對于程序員來說,還是要搬運很多代碼,接下來進(jìn)一步優(yōu)化
b.使用匿名內(nèi)部類
匿名內(nèi)部類:沒有顯示名字的內(nèi)部類
本質(zhì):匿名內(nèi)部類會隱式得繼承一個類或?qū)崿F(xiàn)一個接口,或者說,匿名內(nèi)部類時一個繼承了該類或者實現(xiàn)了該接口的子類匿名對象
格式:new?類名/接口/抽象類(){}

使用匿名內(nèi)部類,就不需要每次都新建一個實現(xiàn)類,直接在方法內(nèi)部實現(xiàn)??雌饋硎遣皇歉啙嵜髁艘稽c。
c.使用Lambda表達(dá)式

Lambda表達(dá)式就是一段更加簡潔明了的匿名內(nèi)部類。
d.還可以使用Stream API

2、函數(shù)式接口
問題一:為什么要新增函數(shù)式接口?
答案是為了配合lambda表達(dá)式使用,當(dāng)接口中存在多個抽象方法的時候,單純使用lambda表達(dá)式并不能智能匹配對應(yīng)的抽象方法,因此引入了函數(shù)式接口的概念
問題二:什么是函數(shù)式接口
概念:只定義了一個抽象方法的接口(Object類的public方法除外),就是函數(shù)式接口,并且還提供了注解:@FunctionallInterface
理解:首先要理解什么是函數(shù),這里的函數(shù)跟編程中的函數(shù)并不一樣,而是與數(shù)學(xué)中函數(shù)的概念一樣——一種映射的關(guān)系,接下來看看分類就就很明了了。
常見的四大函數(shù)式接口:
a.Consumer<T>:消費型接口,有參無返回值

b.Supplier<T>:供給型接口,無參有返回值

c.Function<T,R>:函數(shù)式接口,有參有返回值

d.Predicate<T>:斷言型接口,有參有返回值,返回值是boolean類型

在四大核心函數(shù)式接口基礎(chǔ)上,還提供了諸如BiFunction、BinaryOperation、toIntFunction等擴(kuò)展式的函數(shù)式接口,都是在這四種函數(shù)式接口上擴(kuò)展而來的
三、方法引用和構(gòu)造器調(diào)用
方法引用和構(gòu)造器調(diào)用是在基于Lambda表達(dá)式的基礎(chǔ)之上的:若lambda體中的內(nèi)容有方法已經(jīng)實現(xiàn)了,那么可以使用"方法引用"??梢岳斫鉃榉椒ㄒ檬莑ambda表達(dá)式的另外一種表現(xiàn)形式并且其語法比lambda表達(dá)式更加簡單
1、方法引用
三種表現(xiàn)形式:
a.對象::實例方法名
b.類::靜態(tài)方法名
c.類::實例方法名(lambda參數(shù)列表中的第一個參數(shù)是實例方法的調(diào)用這,第二個參數(shù)是實例方法時可用)

2、構(gòu)造器調(diào)用
格式:ClassName::new

還有一種數(shù)組引用
格式:Type[]::new

4、Stream API
Stream操作的三個步驟:
(1)創(chuàng)建stream

(2)中間操作(過濾、map)

(3)終止操作


還有功能比較強(qiáng)大的兩個終止操作 reduce和collect
reduce操作: reduce:(T identity,BinaryOperator)/reduce(BinaryOperator)-可以將流中元素反復(fù)結(jié)合起來,得到一個值

并行流和串行流:在jdk1.8新的stream包中針對集合的操作也提供了并行操作流和串行操作流。并行流就是把內(nèi)容切割成多個數(shù)據(jù)塊,并且使用多個線程分別處理每個數(shù)據(jù)塊的內(nèi)容。Stream api中聲明可以通過parallel()與sequential()方法在并行流和串行流之間進(jìn)行切換。
jkd1.8并行流使用的是fork/join框架進(jìn)行并行操作
ForkJoin框架:在必要的情況下,將一個大任務(wù),進(jìn)行拆分(fork)成若干個小任務(wù)(拆到不可再拆時),再將一個個的小任務(wù)運算的結(jié)果進(jìn)行join匯總
關(guān)鍵:遞歸分合、分而治之
采用”工作竊取“工作模式(work-stealing):當(dāng)執(zhí)行新的任務(wù)時,它可以將其拆分成更小的任務(wù)執(zhí)行,并將小任務(wù)加到線程隊列中,然后再從一個隨機(jī)線程的隊列中偷一個并把它放到自己的隊列中
相對于一般的線程池實現(xiàn),fork/join框架的優(yōu)勢體現(xiàn)在對其包含的任務(wù)的處理方式上
一般的線程池中,如果一個線程正在執(zhí)行的任務(wù)由于某些原因無法繼續(xù),那么該線程會處于等待狀態(tài)
fork/join框架實現(xiàn)中,如果某個子問題由于等待另外一個子問題的完成而無法繼續(xù)運行,那么處理該子問題的線程會主動尋找其它尚未運行的子問題來執(zhí)行,這個方式減少了線程的等待時間,提高了性能
五、接口中的默認(rèn)方法和靜態(tài)方法
在接口中可以使用default和static關(guān)鍵字來修飾接口中定義的普通方法

在JDK1.8中很多接口會新增方法,為了保證1.8向下兼容,1.7版本中的接口實現(xiàn)類不用每個都重新實現(xiàn)新添加的接口方法,引入了default默認(rèn)實現(xiàn),static的用法是直接用借口名去調(diào)方法既可。當(dāng)一個類繼承父類又實現(xiàn)接口時,若后兩者方法名相同,則有限繼承父類中的同名方法,即”類優(yōu)先“,如果實現(xiàn)兩個同名方法的接口,則要求實現(xiàn)類必須手動聲明默認(rèn)實現(xiàn)哪個接口中的方法
六、新的時間日期API
1、使用過時間日期API的都知道,之前使用的java.util.Date月份從0開始,我們一般會+1使用,很不方便,新的java.time.LocalDate月份和星期都改成了enum;
2、java.util.Date和SimpleDateFormat都不是線程安全的,而LocalDate和LocalTime和最基本的String一樣,是不變類型,不但線程安全,而且不能修改。
之前SimpleDateFormat多線程環(huán)境下一般都是配合ThreadLocal使用,新的LocalDate顯然更加簡潔明了。
3、java.util.Date是一個“萬能接口”,它包含日期、時間,還有毫秒數(shù),更加明確需求取舍
4、新接口更好用的原因是考慮到了日期時間的操作,經(jīng)常發(fā)生往前推或往后推幾天的情況。用java.util.Date配合Calendar要寫好多代碼,而且一般的開發(fā)人員還不一定能寫對。
七、HashMap、ConcurrentHashMap數(shù)據(jù)結(jié)構(gòu)優(yōu)化(面試重點)
面試問題:你能說說HashMap/ConcurrentHashMap的結(jié)構(gòu)嗎?
(一)JDK1.8之前
hashMap采用的是哈希表(數(shù)組+鏈表)
ConcurrentHashMap采用的是哈希表(數(shù)組+鏈表),通過分段鎖機(jī)制來實現(xiàn)線程安全。
1、hashMap默認(rèn)大小16,一個0-15索引的數(shù)組。
2、存儲方式:
(1)首先調(diào)用元素的hashcode方法計算出哈希碼值,經(jīng)過哈希算法算成數(shù)組的索引值;
(2)如果對應(yīng)的索引處沒有元素,直接存放,如果有對象在,那么比較它們的equals方法比較內(nèi)容
a.如果內(nèi)容一樣,后一個value值會將前一個value值覆蓋
b.如果不一樣,后加的會放在前面,形成一個鏈表,形成了碰撞
3、缺點:加載因子:0.75,數(shù)組擴(kuò)容,達(dá)到總?cè)萘康?5%,就進(jìn)行擴(kuò)容,但是無法避免碰撞的情況發(fā)生
(二)JDK1.8之后
hashMap采用的是數(shù)組+鏈表+紅黑樹
ConcurrentHashMap采用的是數(shù)組+鏈表+紅黑樹,通過CAS+Synchronized來實現(xiàn)線程安全
1、當(dāng)碰撞的個數(shù)>8時&&總?cè)萘?gt;64,會有紅黑樹的引入,除了添加之后,效率都比鏈表高
2、鏈表新進(jìn)元素加到末尾
3、ConcurrentHashMap(鎖分段機(jī)制),concurrentLevel,JDK1.8采用CAS算法(無鎖算法,不再使用鎖分段)
4、紅黑樹(自平衡的二叉查找樹,基于二叉查找樹、完美平衡二叉樹)
問題二:HashMap為什么不是線程安全的?
答:HashMap中的變量沒有用volatile關(guān)鍵字修飾來保證可見性和有序性,也沒有用任何鎖來保證原子性,在多線程運行的環(huán)境下,可能會產(chǎn)生并發(fā)問題,如多個線程同時插入時,可能會導(dǎo)致閉環(huán)。
問題三:ConcurrentHashMap怎么保證線程安全?
答:JDK1.7中,ConcurrentHashMap采用鎖分段機(jī)制來保證線程安全同時提高并發(fā)性;JDK1.8中,ConcurrentHashMap采用的是CAS加上Synchronized來保證線程安全,當(dāng)沒有發(fā)生沖突的使用使用CAS來保證線程安全,當(dāng)發(fā)生沖突的時候用Synchronized來鎖住。
PS:有關(guān)鎖可以參考我之前寫的JUC之Locks鎖面經(jīng)整理