讀古今文學網 > Java程序員修煉之道 > 14.1 對Java 8的期待 >

14.1 對Java 8的期待

2010年秋,Java SE執行委員會商議決定執行B計劃。這個決定是盡快發佈Java 7,並把一些主要特性延遲到Java 8中。這一結論是在對社區進行廣泛徵詢和投票後得出的。

在Java 7中發起的某些特性已經被推到Java 8中了,還有些特性已經縮減了範圍以便為將來的特性打下基礎。在這一節中,我們會對一些期望Java 8突出的特性做簡要介紹,包括那些被延遲的特性。在這一階段,沒有什麼是板上釘釘的,特別是語法。所有示例代碼都只是初步構想,可能跟Java 8的最終寫法差別很大。歡迎來到風口浪尖!

14.1.1 lambda表達式(閉包)

將在Java 8中構建的Java 7特性中最有代表性的是MethodHandlesinvokedynamic。它們本身是非常實用的特性(在Java 7中,invokedynamic主要用在語言和框架實現上)。

在Java 8中,這些特性是將lambda表達式引入Java語言的基礎。可以這樣理解,lambda表達式跟前面在備選語言中講的函數字面值類似,它們也能用來解決我們在前面重點強調的那類問題。

就Java 8的語法而言,還需要決定lambda表達式在代碼中如何表示。但其基本特性已經確定下來了,所以我們先來看看基本的Java 8語法,如代碼清單14-1所示。

代碼清單14-1 在Java中用lambda表達式實現Schwartzian變換

public List<T> schwarz(List<T> x, Mapper<T, V> f) {
  return x.map(w -> new Pair<T,V>(w, f.map(w)))
    .sorted((l,r) -> l.hashed.compareTo(r.hashed))
    .map(l -> l.orig).into(new ArrayList<T>);
}
  

schwartz方法看起來應該眼熟,它是在10.3節中用閉包實現的Schwartzian變換。代碼清單14-1展示了Java 8中lambda表達式的下列基本語法:

  • 在lambda表達式前部有個參數列表;
  • 組成lambda表達式的主體代碼塊用括號括起來;
  • 用箭頭(->)來分隔參數列表和lambda表達式的主體;
  • 參數列表中參數的類型是可推斷的。

第9章中Scala的函數字面值和這個寫法很像,所以這種語法應該不會讓你覺得特別陌生。代碼清單14-1中的lambda表達式非常短,全都只有一行。實際上,lambda表達式是可以包含多行代碼的,其主體甚至可以很大。經過初步分析,那些適於改造成lambda表達式的代碼改造後的長度都應該在1到5行之間。

代碼清單14-1中還介紹了另外一個新特性。變量x的類型是List<T>。我們在x上調用了方法mapmap方法接受了一個lambda表達式作為其參數。停!List接口根本就沒有map方法,並且在Java 7及之前都不存在lambda表達式。

我們來仔細看看這個問題是如何解決的。

1. 擴展和默認方法

我們所面臨的問題本質是:怎麼向已有接口中添加方法以使其「lambda化」而又不破壞其向後兼容性?

答案來自於Java的一個新特性:擴展方法。它可以為沒有提供擴展方法的接口實現提供一個可用的默認方法。

這些默認的方法實現必須在接口本身內部定義。比如跟List搭配的AbstractList,跟Map搭配的AbstractMap,跟Queue搭配的AbstractQueue。這些類是為各自的接口存放新的擴展方法默認實現的理想之所。Java內置的集合類是擴展方法和lambda化的主要應用場景,但看起來這種模型在最終用戶代碼中也適用。

Java怎麼實現擴展方法

擴展方法會在類加載時進行處理。當加載一個帶有擴展方法的接口實現時,類加載器會檢查它是否實現了自己的擴展方法。如果沒有,類加載器會定位到默認方法,並把一個橋接方法插入新加載類的字節碼中。這個橋接方法會用invokedynamic調用默認方法。

擴展方法無需打破向後兼容性就可以讓發佈後的接口得到進化。開發人員可以借此帶著lambda表達式為老舊的API注入新的活力。但lambda表達式對於JVM來說是什麼呢?是對象嗎?如果是,它們的類型是什麼?

2. SAM轉換

lambda表達式提供了一種緊湊的辦法,可以聲明少量內聯代碼並將其作為數據傳遞。也就是說lambda是一個對象,就像我們在本書第三部分中對lambda表達式和函數字面值的解釋一樣。具體說來,你可以把lambda表達式當做Object的一個子類,它沒有參數(因此也沒有狀態),只有一個方法。

還有一種理解這個問題的方式:通過術語SAM(Single Abstract Method,單例抽像方法)。SAM的概念在各種Java API中都有體現,是一種常見主題。很多API中都有只聲明了一個單例方法的接口。 RunnableComparableCallableActionListener之類的監聽器都只聲明名了一個方法,因此都算SAM類。

在剛開始用lambda表達式時,可以把它們當做語法糖——為給定接口編寫匿名實現的簡便寫法。過一段時間後,你可以掌握更多的函數式技術,甚至可能會從Scala或Clojure中把自己喜歡的技巧引入Java代碼中。學習函數式編程是個循序漸進的過程:從集合的映射、排序和過濾技術開始學起,然後慢慢向外開疆拓土。

現在讓我們進入下一個大主題:模塊化編程,它正在Jigsaw項目的支持下如火如荼地展開。

14.1.2 模塊化(拼圖Jigsaw)

處理classpath有時毫無疑問是不太理想的。圍繞著JAR文件和classpath構建的生態系統有些眾所周知的問題:

  • JRE自身的規模就比較大;
  • JAR文件提倡整體式部署模型;
  • 有些繁瑣且極少會用到的類仍然必須加載;
  • 啟動慢;
  • classpath是脆弱的野獸,並且跟機器上的文件系統結合得過於緊密;
  • classpath基本上是一個扁平化的命名空間;
  • JAR不具有固有的版本;
  • 即便邏輯上沒有關聯的類之間也有複雜的相互依賴關係。

要解決這些問題,需要一個新的模塊系統。但要先解決架構上的問題。其中最重要的如圖14-1所示。

圖14-1 模塊化系統的架構選擇

我們是應該引導VM然後再使用用戶層模塊化系統(比如OSGi),還是應該徹底遷移到模塊化平台上?

後一種方式需要啟動一個能支持模塊的最基本的 \"內核\" VM,然後根據啟動應用程序的需要添加特定的模塊。這要求對VM,以及JRE中很多現有的類做顛覆性的修改,但潛在收益更大。

  • JVM應用程序的啟動時間可以跟shell和腳本語言相媲美。

  • 能顯著降低應用程序部署的複雜性。

  • 針對性的Java安裝所佔用的資源可以顯著減少(對硬盤、內存和安全性都有積極影響)。如果你不需要CORBA或RMI,就不用裝!

  • 可以以更加靈活的方式升級Java安裝。如果在Collections中發現了一個嚴重的bug,只有那個模塊需要升級。

撰寫本書時看起來Jigsaw項目會選擇第二種方式。但在它發佈並能投入使用之前,還有很長的路要走。下面是一些仍在討論的重要問題:

  • 平台或應用的正確發佈單元是什麼?
  • 是不是需要一種跟包和JAR都不同的新結構?

這一設計決策的影響極為重要:Java**無處不在**,因而這種模塊化的設計要滲透到所有地方。它也要支持跨OS平台。

Java平台最起碼要能在Linux、Solaris、Windows、Mac OS X、BSD Unix和AIX上部署模塊化應用。這些平台中有些有需要Java模塊集成的包管理器(比如Debian的apt、Red Hat的rpm,以及Solaris包)。而其他平台,比如Windows,沒有可供Java使用的包管理系統。

這一設計還有其他的限制。不過這一領域已經有一些成熟的項目了:比如Maven和Ivy這樣的依賴項管理系統,還有發起倡議的OSGi。新的模塊化系統應該盡一切可能跟現有項目集成,即便在完全的集成和兼容被證明不可能之後,也應該提供一個順暢的升級途徑。

不管將來會怎樣,Java 8的發佈應該會給Java應用程序的交付和部署帶來革命性的變化。

我們去看看JDK 8應該給JVM的其他公民帶來的一些特性,包括我們在前面研究的那些語言。