編程沒(méi)有銀彈:探討Java8新增特性的優(yōu)缺點(diǎn)
Java 8或許是 迄今為止最令人期待的Java版本,最初定于今年的9月份發(fā)布,但由于一系列的安全漏洞問(wèn)題,目前已推遲到明年的3月份。

Java 8試圖“創(chuàng)新”,根據(jù) 微軟對(duì)這個(gè)詞的定義,就是把其他框架或語(yǔ)言里成熟的特性“偷”進(jìn)來(lái)。在新版本發(fā)布之前,Java社區(qū)就已經(jīng)開始討論Lambda項(xiàng)目、Streams、函數(shù)式接口等其他好東西。下面就讓我們一起來(lái)看下這些偉大的功能,看看它們各自的優(yōu)缺點(diǎn),好讓你更好地應(yīng)用在項(xiàng)目中。
Streams
集合(Collections)的改進(jìn)也是Java 8的一大亮點(diǎn),而讓集合越來(lái)越好的核心組件則是“Stream”。它與java.io包里的InputStream和OutputStream是完全不同的概念,它是一個(gè)全新的概念,大家不要混淆。
此外,Stream的出現(xiàn)也并不是要取代ArrayLists或其他集合,它提供了一種操作大數(shù)據(jù)接口,讓數(shù)據(jù)操作更容易和更快。Stream是 一次性使用對(duì)象,一旦被遍歷,就無(wú)法再次遍歷。在遍歷時(shí),它具有過(guò)濾、映射以及減少遍歷數(shù)等功能。每個(gè)Stream都有兩種模式:順序執(zhí)行和并行執(zhí)行,其 能夠利用多核處理器的優(yōu)勢(shì),并可以使用 fork/join并行方式來(lái)拆分任務(wù)和加速處理過(guò)程。
順序流:
- List <Person> people = list.getStream.collect(Collectors.toList());
并行流:
- List <Person> people = list.getStream.parallel().collect(Collectors.toList());
顧名思義,當(dāng)使用順序方式去遍歷時(shí),每個(gè)item讀完后再讀下一個(gè)item。而使用并行去遍歷時(shí),數(shù)組會(huì)被分成多個(gè)段,其中每一個(gè)都在不同的線程中處理,然后將結(jié)果一起輸出。
并行流實(shí)例:
- List originalList = someData;
- split1 = originalList(0, mid);
- split2 = originalList(mid,end);
- new Runnable(split1.process());
- new Runnable(split2.process());
- List revisedList = split1 + split2;
由于一個(gè)Stream只能被遍歷一次,通常會(huì)返回另外一個(gè)Stream,可以使用終端方法(terminal method)來(lái)獲取有用的結(jié)果,終端方法可以是sum()、collect()或toArray()等。在Stream被終止之前,操作的結(jié)果不會(huì)被實(shí)現(xiàn)。
- Double result = list.getStream().mapToDouble(f -> f.getAmount()).sum();
- List<Person> people = list.getStream().filter(f -> f.getAge() > 21).collect(Collectors.toList());
該功能最大的好處是允許使用多核處理器來(lái)處理集合,這樣處理速度會(huì)更加快速。而最主要的問(wèn)題則是可讀性。隨著流鏈的加長(zhǎng),很有可能影響可讀性。其它問(wèn)題則來(lái)源于內(nèi)置的新東西來(lái)支持這個(gè)新路徑,這些是功能接口和Lambda。
函數(shù)式接口
在Java 8里將會(huì)有一個(gè)全新的功能——函數(shù)式接口(functional interfaces),就是可以在接口里面添加默認(rèn)方法,并且這些方法可以直接從接口中運(yùn)行。
這樣就可以在接口中實(shí)現(xiàn)集合的向后兼容,并且無(wú)需改變實(shí)現(xiàn)這個(gè)方法的類,就可以讓Stream放置到接口中。一般而言,在接口中創(chuàng)建一個(gè)默認(rèn)方法,然后實(shí)現(xiàn)該接口的所有類都可以使用Stream(無(wú)論是默認(rèn)方法還是非默認(rèn)方法)。
基本上就是一種多繼承形式,這樣就變成了實(shí)現(xiàn)者之間的問(wèn)題,作為實(shí)現(xiàn)人員,必須重寫這些方法,他們可以選擇使用超方法(supermethod),這也就意味著,許多實(shí)現(xiàn)接口的類需要改寫。
這有可能是Java 8里最讓人關(guān)心的細(xì)節(jié),也許Java 8里的函數(shù)式接口對(duì)于熟悉Scala的開發(fā)者來(lái)說(shuō)不算新功能,但是他們可能會(huì)拿函數(shù)式接口與Scala的特征進(jìn)行比較。然而,兩者之間不同的是:Java 8里的函數(shù)式接口不能將一個(gè)引用指向?qū)崿F(xiàn)類,而Scala允許通過(guò)self關(guān)鍵字來(lái)實(shí)現(xiàn)該操作。會(huì)有一些語(yǔ)言狂熱者說(shuō),Java 8里的函數(shù)式接口只允許多繼承行為,而不是狀態(tài)。而Scala里的多繼承特征既可以是行為也可以是狀態(tài)。
在Java里實(shí)現(xiàn)事務(wù)和其它項(xiàng)目,我們一般會(huì)使用 JavaAssist或 cglib的擴(kuò)展類來(lái)構(gòu)建動(dòng)態(tài)代理和字節(jié)碼操作。而Scala的特行可以讓我們更直接地實(shí)現(xiàn)。
一方面,函數(shù)式接口可能會(huì)被以繼承方式濫用,另一方面,它們盡量不與Scala特征重復(fù)。
Lambda
Java 8的另一大亮點(diǎn)是引入Lambda表達(dá)式,使用它設(shè)計(jì)的代碼會(huì)更加簡(jiǎn)潔。當(dāng)開發(fā)者在編寫Lambda表達(dá)式時(shí),也會(huì)隨之被編譯成一個(gè)函數(shù)式接口。下面這個(gè)例子就是使用Lambda語(yǔ)法來(lái)代替匿名的內(nèi)部類,代碼不僅簡(jiǎn)潔,而且還可讀。
沒(méi)有使用Lambda的老方法:
- button.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent ae) {
- System.out.println(“Action Detected”);
- }
- }
- );
使用Lambda:
- button.addActionListener(e -> {
- System.out.println(“Action Detected”);
- }
- );
#p#
讓我們來(lái)看一個(gè)更明顯的例子。
不采用Lambda的老方法:
正如你所看到的,使用Lambda表達(dá)式不僅讓代碼變的簡(jiǎn)單、而且可讀、最重要的是代碼量也隨之減少很多。然而,在某種程度上,這些功能在Scala等這些JVM語(yǔ)言里已經(jīng)被廣泛使用。
并不奇怪,Sclala社區(qū)是難以置信的,因?yàn)樵S多Java 8里的內(nèi)容看起來(lái)就像是從Scala里搬過(guò)來(lái)的。在某種程度上,Java 8的語(yǔ)法要比Scala的更詳細(xì)但不是很清晰,但這并不能說(shuō)明什么,如果可以,它可能會(huì)像Scala那樣構(gòu)建Lambda表達(dá)式。
一方面,如果Java繼續(xù)圍繞Lambda來(lái)發(fā)展和實(shí)現(xiàn)Scala都已經(jīng)實(shí)現(xiàn)的功能,那么可能就不需要Scala了。另一方面,如果它只提供一些 核心的功能,例如幫助匿名內(nèi)部類,那么Scala和其他語(yǔ)言將會(huì)繼續(xù)茁壯成長(zhǎng),并且有可能會(huì)凌駕于Java之上。其實(shí)這才是最好的結(jié)果,有競(jìng)爭(zhēng)才有進(jìn)步, 其它語(yǔ)言繼續(xù)發(fā)展和成長(zhǎng),并且無(wú)需擔(dān)心是否會(huì)過(guò)時(shí)。
Java time
Time在Java里已有很長(zhǎng)一段時(shí)間,首先出現(xiàn)的java.util.Date這個(gè)包,其次還有java.sql.Date、 Calendar。但處理時(shí)間和日期需要大量的monkey代碼,因此,像Joda Time等第三方庫(kù)因此誕生。姍姍來(lái)遲,Oracle終于決定在Java里添加一個(gè) java.time包來(lái)清理各種時(shí)間接口。它看起來(lái)很符合現(xiàn)在開發(fā)者的胃口,擁有各種各樣的時(shí)間API。
Java API可以處理一些時(shí)空連續(xù)體方面的特性,比如距離、質(zhì)量、重量等,這是值得稱贊的,但我仍然認(rèn)為 Currency會(huì)處理得更好。我認(rèn)為Java API需要好好地修剪而不是添加更多的東西,并且首先Java API應(yīng)該對(duì)這些基本元素提供標(biāo)準(zhǔn)的兼容。
Nashorn
Nashorn是Rhino的接替者,該項(xiàng)目的目的是基于Java實(shí)現(xiàn)一個(gè)輕量級(jí)高性能的JavaScript運(yùn)行環(huán)境。
JDK 7中添加了invokeDynamic,其主要是用來(lái)支持非Java語(yǔ)言,尤其是動(dòng)態(tài)語(yǔ)言。而JDK 8中的Nashorn將會(huì)給開發(fā)者提供一個(gè)更加實(shí)用的JavaScript實(shí)現(xiàn)。事實(shí)上,Oracle已經(jīng)有了他自己的Node.js實(shí)現(xiàn),叫做 Node.jar。這似乎比在Java里運(yùn)行JavaScript更加吸引人。
Accumulators
自從JDK中集成了 java.util.concurrent以來(lái),該特性并沒(méi)有停止發(fā)展。相反,JDK 8將構(gòu)建于JDK 7和fork/join框架之上,并通過(guò)加法器(adders)和累加器(Accumulators)得到了進(jìn)一步的發(fā)展。
首先是同步。但是,如果你使用同步在多線程之間進(jìn)行增量計(jì)數(shù),那么同步有可能難以負(fù)擔(dān)。在Java 6中通過(guò)讓非競(jìng)爭(zhēng)鎖更廉價(jià)(cheap)來(lái)使同步不那么難以負(fù)擔(dān)。其中大多數(shù)會(huì)使用Vector來(lái)提升老應(yīng)用程序性能,幾乎每一個(gè)單線程都受到了Java Activation Framework的影響。
Java.util.concurrent包使得線程池和其他相對(duì)復(fù)雜的多線程結(jié)構(gòu)變得更好,但是,倘若你想要通過(guò)跨線程來(lái)增加一個(gè)變量,那么就 有點(diǎn)大材小用了。對(duì)此,我們采用一種比真正的鎖更輕更快的原子。在JDK 8中,我們采用Accumulators和adders,這些要比原子輕量多了,對(duì)于大多數(shù)異構(gòu)代碼來(lái)說(shuō),這些足以滿足它們的需求,如果線程太多,那么可 以增加一個(gè)計(jì)數(shù)器。但想要看到類似map/reduce實(shí)現(xiàn)或統(tǒng)計(jì)跨線程之間的總和,你仍然需要使用原子,因?yàn)槿绻x取這些跨線程的值,累積的順序是無(wú) 法得以保證的。
HashMap修復(fù)
在Java中使用String.hashCode()實(shí)現(xiàn)已是大家熟知的bug。如果在特定的代碼中引入HashMap,可能會(huì)導(dǎo)致拒絕服務(wù)攻擊?;旧?,如果有足夠多的參數(shù)hash到相同值,那么可能會(huì)消耗過(guò)多的CPU時(shí)間。
通常,HashMap bucket采用鏈表的方式來(lái)存儲(chǔ)map條目。使用此算法存在大量的沖突,并且增加了O(1)到O(N)這種哈希變化的復(fù)雜性,為了解決這一問(wèn)題,通過(guò)采用平衡tree算法來(lái)降低復(fù)雜度。
TLS SNI
SNI是 服務(wù)器名稱標(biāo)識(shí)(Server Name Identification)的縮寫,由于大多數(shù)公共網(wǎng)站的訪客數(shù)量不是太多,幾乎很少能達(dá)到數(shù)百萬(wàn)用戶。很多網(wǎng)站都使用相同的IP地址和基于名字的虛擬主機(jī),比如我訪問(wèn) podcasts.infoworld.com和 [/url][url=http://www.infoworld.com/]www.infoworld.com, 最后的網(wǎng)址是一樣的,但訪問(wèn)的主機(jī)名是不一樣的,所以我有可能會(huì)訪問(wèn)到不同的Web頁(yè)面。然而,因?yàn)镾SL,我可能無(wú)法分享IP地址。由于HTTP主機(jī)頭 是建立在基于命名的虛擬主機(jī)上,并且主機(jī)也是依賴SSL來(lái)實(shí)現(xiàn)加密/解密的,所以,不得不為每個(gè)SSL證書申請(qǐng)不同的IP地址。
在最近幾年都是采用SNI來(lái)解決這一問(wèn)題的,Java也不例外。這種方式得到了大多數(shù)瀏覽器的支持,現(xiàn)在Apache和Java也支持它。這意味著過(guò)不了多久,我們就可以看到Apache和基于Java的服務(wù)器使用Oracle的SSL實(shí)現(xiàn)來(lái)支持SNI,稱作 JSSE。
總結(jié)
總之,Java 8包含了一大堆非常實(shí)用的特性,這也是許多開發(fā)者想使用最新版本的原因之一。在我看來(lái),Stream是最好的一個(gè)特性。但愿并行集合也能夠?yàn)槠溥M(jìn)程性能帶來(lái)一些提升。而函數(shù)式接口可能并不會(huì)像預(yù)期中的那樣好用,萬(wàn)一使用不當(dāng),可能會(huì)給開發(fā)者帶來(lái)很多麻煩。
本文只是總結(jié)了部分Java 8新特性,我們相信,在發(fā)布的時(shí)候?qū)?huì)有更多新特性與大家見(jiàn)面。你可以通過(guò)Simon Ritter在JavaOne 2013大會(huì)上的演講PPT來(lái)了解目前已經(jīng)添加到Java 8中的55個(gè)新特性。
至于該如何取舍,各位開發(fā)者應(yīng)該根據(jù)自己的實(shí)際需求去研究和使用,并不是所有的新特性就是好的,它們也存在優(yōu)缺點(diǎn)。