不要急於切換到Java 8的6個原因
Java 8是極好的。不過我們在嘗完鮮了之後,也開始對它持懷疑的態度。所有好的東西都是有代價的,在這篇文章中,我會分享Java 8的主要的幾個難點。在你放棄Java 7升級到8之前,你最好確保自己知道這些。
並行流會影響效能
Java 8的所承諾的並行處理是最受期待的新特性之一。集合以及流上的.parallelStream()方法就是實現這點的。它將問題分解成子問題,然後分別執行在不同的執行緒上,它們可能會被分配到不同的CPU核上,當完成之後再組合起來。這些全都是在底層通過fork/join框架來實現的。好的,聽起來很酷吧,在多核環境下的大資料集上,這麼做肯定能提升操作速度的,對吧?
不,如果你用的不對的話,這麼做可能會讓你的程式碼變得更慢。在我們執行的基準測試上大概是慢了15%左右,而且還有可能會更糟。假設我們已經是執行在多核環境中了,我們又使用了.parallelStream(),將更多的執行緒加入了執行緒池中。這很可能會超出我們的核數的處理能力,並且由於上下文切換,會導致效能出現下降。
下面是我們的一個效能變差的基準測試,它是要將一個集合分到不同的組裡(素數或者非素數):
Map<Boolean, List<Integer>> groupByPrimary = numbers .parallelStream().collect(Collectors.groupingBy(s -> Utility.isPrime(s)));
還有別的原因可能會讓它變得更慢。考慮下這種情況,假設我們有多個任務要完成,其中一個可能花費的時間比其它的更長。將它用.parallelStream() 進行分解可能會導致更快的那些任務完成的時間往後推遲。看下Lukas Krecan的這篇文章,裡面有更多的一些例子以及程式碼。
診斷:並行處理帶來好處的同時也帶來了許多額外的問題。當你已經是處於一個多核環境中了,你要時刻牢記這點,並要弄清楚事情表面下所隱藏的本質。
Lambda表示式的負作用
Lambda。喔,Lambda。儘管沒有你,我們也什麼都可以做,但是你讓我們變得更優雅,減少了許多樣板程式碼,因此大家都很容易會喜歡上你。假設一下早上我起床了想要遍歷世界盃的一組球隊,然後計算出它們的長度:
List lengths = new ArrayList(); for (String countries : Arrays.asList(args)) { lengths.add(check(country)); }
如果有了Lambda我們就可以使用函式式了:
Stream lengths = countries.stream().map(countries -> check(country));
這太牛了。儘管很多時候它都是件好事,不過把Lambda這樣的新元素增加到Java中使得它有點偏離了最初的設計規範。位元組碼是完全物件導向的,但同時這個遊戲裡又帶上了lambda,實際的程式碼和執行時之間的差別變得越來越大了。可以讀下Tal Weiss的這篇文章,瞭解更多關於lambda表示式的一些陰暗面。
最後,這意味著你所寫的和你所除錯的完全是兩個不同的東西。棧資訊會變得越來越大,這使得你除錯程式碼變得更加費勁了。
將空串增加到列表裡,原先只是這麼簡單的一個棧資訊:
at LmbdaMain.check(LmbdaMain.java:19) at LmbdaMain.main(LmbdaMain.java:34)
現在變成了:
at LmbdaMain.check(LmbdaMain.java:19) at LmbdaMain.lambda$0(LmbdaMain.java:37) at LmbdaMain$$Lambda$1/821270929.apply(Unknown Source) at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193) at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512) at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502) at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708) at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) at java.util.stream.LongPipeline.reduce(LongPipeline.java:438) at java.util.stream.LongPipeline.sum(LongPipeline.java:396) at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526) at LmbdaMain.main(LmbdaMain.java:39)
lambda表示式引起的另一個問題就是過載:由於lambda的引數必須得強制轉化成某個型別才能進行方法呼叫,而它們可以轉化成好幾個型別,這可能會導致呼叫發生歧義。Lukas Eder通過程式碼示例說明了這點。
診斷:記住這點,棧跟蹤資訊可能會成為一種痛苦,不過這並不會阻擋我們使用lambda的腳步。
預設方法使人困惑
預設方法使得介面方法的預設實現成為了可能。這的確是Java 8帶來的一個非常酷的新特性,但是它多少影響了我們之前所習慣的做事的方式。那為什麼還要引入它呢?什麼時候不應該使用它?
預設方法背後最大的動機應該就是如果我們需要給現有的一個介面增加方法的話,我們可以不用重寫介面的實現。這使得它可以相容老的版本。比如說,下面是從Oracle官方的一個Java教程中拿過來的一段程式碼,它是要給一個指定的時區新增某個功能:
public interface TimeClient { // ... static public ZoneId getZoneId (String zoneString) { try { return ZoneId.of(zoneString); } catch (DateTimeException e) { System.err.println("Invalid time zone: " + zoneString + "; using default time zone instead."); return ZoneId.systemDefault(); } } default public ZonedDateTime getZonedDateTime(String zoneString) { return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString)); } }
問題解決了。是嗎?但預設方法將介面及實現弄得有點混淆。型別結構自己是不會糾纏到一起的,所以現在我們得好好馴服下這個新生物了。可以讀下RebelLabs的Oleg Shelajev的這篇文章。
診斷:當你掄起錘子的時候看誰都像顆釘子,記住了,要堅持原始的用例,將一個現有的介面重構成一個新的抽象類是不會有什麼用處的。
下面講的這些,要麼是漏掉的,要麼是該刪除卻仍在的,或者是還沒有完全實現的:
為什麼是Jigsaw
Jigsaw專案的目標是使得Java可以模組化,並將JRE分解成能互相協作的不同元件。專案的初衷是希望Java可以更好,更快,更強地進行嵌入。我已經儘量避擴音起“物聯網”了,但剛才實際已經說到了。減少JAR包的大小,提升效能,提高安全性,這些也是這個專案的一些願景。
那麼它怎麼樣了?Jigsaw目前已經通過了探索性的階段,進入了第二階段了,目前將致力於設計及實現能達到上線質量的產品,Oracle的首席架構師Mark Reinhold如是說。這個專案原本是計劃隨著Java8釋出的,後來被推遲到了Java 9,這也是9中倍受期待的新特性之一。
遺留的問題
受檢查異常
大家都不喜歡模板程式碼,這也是為什麼lambda會如此流行的原因之一。想像一下異常的樣板程式碼吧,不管你是不是需要捕獲或者處理這些受檢查異常,你都得去捕獲它。儘管是根本不可能發生 的事情,就像下面這個一樣,它壓根兒就不會發生 :
try { httpConn.setRequestMethod("GET"); } catch (ProtocolException pe) { /* Why don’t you call me anymore? */ }
基礎型別
它們還在這裡,要想正確地使用它們簡直是種痛苦。正是它使得Java無法成為一門純粹的物件導向的程式語言,並且其實上移除它們對效能也沒有太大的影響。新的JVM語言裡也都沒有基礎型別。
操作符過載
Java之父James Gosling曾在一次採訪中說道:“我沒有采用操作符過載這完全是我個人的喜好,因為我看過太多人在C++中濫用它了”。這也有一定的道理,不過也有不少反對的聲音。別的JVM語言也提供了這一特性,但另一方面,它可能會導致下面這樣的程式碼:
javascriptEntryPoints <<= (sourceDirectory in Compile)(base => ((base / "assets" ** "*.js") --- (base / "assets" ** "_*")).get )
這是Scala的Play框架中的一行真實的程式碼,我已經有點崩潰了。
診斷:這些真的算是問題嗎?我們都有自己的怪癖,這些也算是Java的吧。未來的版本可能會有驚喜,這些也可能會變,但是向後相容的問題也在那裡等著我們了。
函數語言程式設計
Java之前也可以進行函數語言程式設計,雖然說有點勉強。Java 8通過lambda以及別的一些東西改進了這一狀態。這確實是最受歡迎的特性,不過並沒有之前傳說中的那麼大的變化。它是比Java 7要優雅多了,不過要想成為真正的函式式語言還有很長的路要走。
關於這個問題最激烈的評論應該是來自Pierre-yves Saumont 的這一系列的文章了,他詳細比較了函數語言程式設計的正規化和Java的實現方式之間的區別。
那麼該用Scala還是Java?Java採用了更現代的函式式正規化也算是對Scala的一種認可,後者提供lambda也有一段時間了。lambda確實是獨領風騷,但是還有許多別的特性比如說trait,惰性求值,不可變性等,它們也是Scala不同於Java之處。
診斷: 不要被lambda分心了,Java 8算不算函數語言程式設計仍在爭論當中。
相關文章
- 切換到Git的8個理由Git
- 探討寶塔切換php版本切換失敗的原因和解決方法PHP
- 動態切換JDK8和JAVA17JDKJava
- 切換java版本Java
- RVM切換到rbenv[MacOS]Mac
- 關於PHP的切換版本PHP
- ubuntu切換java版本UbuntuJava
- 關於 Java 8 的6大頭疼問題Java
- 切換到ZSH以後遇到的坑
- git切換到指定目錄Git
- git切換到遠端分支Git
- 透徹講解,Java執行緒的6種狀態及切換Java執行緒
- Java 8 Stream API 轉換到 Kotlin 集合APIJavaAPIKotlin
- github從一個倉庫切換到另一倉庫Github
- 為什麼我從Java切換到Rust? Opensource.comJavaRust
- 含有replication環境的sqlserver切換到standbySQLServer
- 併發程式設計——Java執行緒的6種狀態及切換程式設計Java執行緒
- Android 如何切換到 Transform API?AndroidORMAPI
- 耐克公司是如何將API切換到GraphQL的?API
- Windows 8讓開發者痛苦的10個原因Windows
- 實現一個切換配方的功能
- 對於實時大庫的切換方法
- 如何從 Docker Desktop 切換到 ColimaDocker
- 從NodeJS切換到Ruby on Rails - nikodunkNodeJSAI
- 搜狗輸入法切換到半形
- Java的CQRS和事件溯源ES入門:如何從CRUD切換到CQRS/ES - BaeldungJava事件
- 彭老師:關於SimpleJdonFrameworkTest的執行問題,急!急!急!急!急!急!Framework
- ES6 Async/Await 完爆Promise的6個原因AIPromise
- Win10怎樣切換平板模式_win10如何切換到平板模式Win10模式
- Flutter 多個版本切換控制Flutter
- 實施ERP系統切換時的“三要”和“三不要”(轉)
- Win8metro介面中的SkyDrive應用怎麼切換登出或切換使用者?
- 如何把Mac上的 Wifi 切換到5GHzMacWiFi
- 9204RAC到單例項的SWITCHOVER切換單例
- 企業使用者選擇Java多於.NET的 5個原因Java
- win10怎麼切換到桌面模式_win10平板模式和桌面模式的切換教程Win10模式
- 關於歸檔日誌的切換測試
- 基於日出和日落時間自動切換到明/暗 Gtk 主題