Java 9新特性,第1部分:新增Factory方法

發現已添加到Java 9Collections Framework中的新的便利工廠方法

Java 9新特性,第1部分:新增Factory方法

除了進一步的延遲之外,Java 9將於7月27日達到通用狀態。其模塊系統和Java Shell讀取 - 評估 - 打印循環(REPL)工具正在受到相當大的關注,但是Java 9還提供了額外的增強功能,將使此版本難以忘懷。

我創建了一系列的帖子,探討其他一些新的增強功能。這個系列試圖回答你至少有一些關於這些產品的問題。我們將著重於添加到Java Collections Framework中的各種接口的新的便捷工廠方法。

編譯並運行系列代碼

我正在使用JDK 9早期訪問版本的build 154編譯並運行本系列中的所有代碼。

方便工廠採集方法

Java增強建議(JEP)269:集合的便利工廠方法定義了幾種工廠方法,用於方便地創建具有少量元素的不可修改集合和映射的實例。本節介紹這些方法後,為什麼它們是必要的。

需要方便的工廠方法

Java經常被批評為冗長。例如,創建一個小的,不可修改的集合(例如,列表)涉及構造它,將其引用存儲在局部變量中,add()通過引用多次調用,最後包裝集合以獲得不可修改的視圖。請考慮以下示例:

List<String> list = new ArrayList<>();

list.add("a");

list.add("b");

list.add("c");

list = Collections.unmodifiableList(list);

這個詳細示例不能簡化為單個表達式,這意味著靜態(不可更改)集合必須在靜態初始化程序塊中填充,而不是通過更方便的字段表達式。但是,有一些替代方法可以指定單個表達式:

List<String> list1 =

Collections.unmodifiableList(new ArrayList<>(Arrays.asList("a", "b", "c")));

List<String> list2 =

Collections.unmodifiableList(new ArrayList<String>() {{ add("a"); add("b"); add("c"); }});

List<String> list3 =

Collections.unmodifiableList(Stream.of("a", "b", "c").collect(toList()));

第一行java.util.List從另一個填充List,從中返回java.util.Arrays.asList()。該示例仍然有些冗長,需要List創建第二個對象(最終是垃圾回收),並且創建第二個對象List可能不是很明顯的。

第二行使用一個匿名內部類的實例初始化器構造來實現減少的冗長度。然而,這種技術是非常模糊的,並且在每次使用時花費額外的課程。它還包含對包圍實例和任何捕獲對象的隱藏引用。最終,可能會發生內存洩漏和/或序列化問題。

第三行使用Java 8的Streams API來實現所需的結果。雖然較少冗長,但它涉及到一定量的不必要的對象創建和計算。此外,Streams不能以這種方式用於構造java.util.Map,除非可以從密鑰計算值,或者流元素包含鍵和值。

這些潛在解決方案的問題導致引入了JEP 186:集合文字,它主張將集合文字添加到Java語言。甲文字集合是一個句法表達式,其值的陣列,List,Map,或其它聚合類型。請考慮以下原始示例的簡明表示:

List<String> list = #[ "a", "b", "c" ];

沒有新的語言功能像人們可能想象的一樣簡單或乾淨,這就是為什麼收集文字沒有添加到Java 9.相反,Java 9提供了工廠方法,為創建小型不可修改的收集/映射實例提供了很多好處,但與改變語言相比,成本和風險大大降低。

探索工廠方法

JEP 269的工廠方法受到類java.util.Collections和java.util.EnumSet類的工廠方法的啟發。Collections提供用於創建空Lists,java.util.Sets和Maps的工廠方法,以及創建具有正好一個元素或鍵值對的單例Lists,Sets和Maps。EnumSet提供了幾個重載的of(...)工廠方法,它們採用固定或可變數量的參數,以便方便地EnumSet使用指定的元素創建。Java 9模型EnumSet的of()方法提供一致和通用的方式來創建包含任意類型對象的Lists,Sets和Maps。

以下工廠方法已添加到List界面中:

static <E> List<E> of()

static <E> List<E> of(E e1)

static <E> List<E> of(E e1, E e2)

static <E> List<E> of(E e1, E e2, E e3)

static <E> List<E> of(E e1, E e2, E e3, E e4)

static <E> List<E> of(E e1, E e2, E e3, E e4, E e5)

static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6)

static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7)

static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8)

static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9)

static <E> List<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)

static <E> List<E> of(E... elements)

以下工廠方法已添加到Set界面中:

static <E> Set<E> of()

static <E> Set<E> of(E e1)

static <E> Set<E> of(E e1, E e2)

static <E> Set<E> of(E e1, E e2, E e3)

static <E> Set<E> of(E e1, E e2, E e3, E e4)

static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5)

static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6)

static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7)

static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8)

static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9)

static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)

static <E> Set<E> of(E... elements)

在每個方法列表中,第一種方法創建一個空的不可修改的集合。接下來的10種方法可創建最多10個元素的不可修改集合。儘管他們的API混亂,但是這些方法避免了最終的varargs方法產生的數組分配,初始化和垃圾回收開銷,這種方法支持任意大小的集合。

清單1演示List和Set工廠方法。

清單1.演示收集工廠方法

import java.util.List;

import java.util.Set;

public class ColDemo

{

public static void main(String[] args)

{

List<String> fruits = List.of("apple", "orange", "banana");

for (String fruit: fruits)

System.out.println(fruit);

try

{

fruits.add("pear");

}

catch (UnsupportedOperationException uoe)

{

System.err.println("unable to modify fruits list");

}

Set<String> marbles = Set.of("aggie", "alley", "steely");

for (String marble: marbles)

System.out.println(marble);

try

{

marbles.add("swirly");

}

catch (UnsupportedOperationException uoe)

{

System.err.println("unable to modify marbles set");

}

}

}

編譯清單1如下:

javac ColDemo.java

運行生成的應用程序如下:

java ColDemo

我在一次運行中觀察到以下輸出:

apple

orange

banana

unable to modify fruits list

steely

alley

aggie

unable to modify marbles set

以下工廠方法已添加到Map界面中:

static <K,V> Map<K,V>

of()

static <K,V> Map<K,V>

of(K k1, V v1)

static <K,V> Map<K,V>

of(K k1, V v1, K k2, V v2)

static <K,V> Map<K,V>

of(K k1, V v1, K k2, V v2, K k3, V v3)

static <K,V> Map<K,V>

of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4)

static <K,V> Map<K,V>

of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5

static <K,V> Map<K,V>

of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6)

static <K,V> Map<K,V>

of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7

static <K,V> Map<K,V>

of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7,

K k8, V v8)

static <K,V> Map<K,V>

of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7,

K k8, V v8, K k9, V v9)

static <K,V> Map<K,V>

of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7,

K k8, V v8, K k9, V v9, K k10, V v10)

static <K,V> Map<K,V>

ofEntries(Map.Entry<? extends K,? extends V>... entries)

第一種方法創建一個空的不可修改的地圖。接下來的10種方法可以創建最多10個鍵/值條目的不可修改的地圖。這些方法增加了一些API雜亂,但是避免了由最終的varargs方法引起的數組分配,初始化和垃圾收集開銷,支持任意大小的地圖。

雖然varargs方法類似於等效的varargs方法for List和Set,它要求每個鍵值對被框。Map可以靜態導入的以下新方法使方便的鍵和值到地圖條目中:

Map.Entry<K,V> entry(K k, V v)

清單2演示Map的ofEntries()和entry()方法。

清單2.演示地圖工廠方法

import java.util.Map;

import static java.util.Map.entry;

public class MapDemo

{

public static void main(String[] args)

{

Map<String, String> capCities =

Map.ofEntries(entry("Manitoba", "Winnipeg"),

entry("Alberta", "Edmonton"));

capCities.forEach((k, v) ->

System.out.printf("Key = %s, Value = %s%n", k, v));

try

{

capCities.put("British Columbia", "Victoria");

}

catch (UnsupportedOperationException uoe)

{

System.err.println("unable to modify capCities map");

}

}

}

編譯清單2如下:

javac MapDemo.java

運行生成的應用程序如下:

java MapDemo

我在一次運行中觀察到以下輸出:

Key = Alberta, Value = Edmonton

Key = Manitoba, Value = Winnipeg

unable to modify capCities map

請注意,JDK的未來版本可能會通過使用值類型來減輕拳擊費用。的entry()便利方法返回一個實現一個新導入的具體類型Map.Entry,以便於潛在未來遷移到值類型。

建築細節

該Collections班為創建不可修改的包裝方法ListS,SetS,和Map秒。這些方法不會產生固有的不可修改的集合/映射。而是採取另一個收集/地圖,並將其包裝在拒絕修改請求的類中,創建原始集合/映射的不可修改視圖。擁有對基礎集合/映射的引用仍然允許修改。每個包裝器是一個額外的對象,需要與原始集合/映射相比的另一層次的間接和消耗更多的內存。最後,包裝收藏/地圖仍然承擔支持突變的代價,即使它從來沒有被修改。新的工廠方法不是這樣。

提供用於創建小型,不可修改的集合/映射的工廠方法滿足大量用例,並且有助於保持規範和實現簡單。不可修改的集合/地圖避免了製作防禦性副本的需要,並且更適合並行處理。此外,小集合/映射佔用的運行時空間很重要。java.util.HashSet使用Collections包裝方法的兩個元素的不可修改的直接創建將由六個對象組成:包裝器,HashSet包含一個java.util.HashMap,其桶表(數組)和每個元素的一個節點實例。與存儲的數據量相比,這是很多開銷,並且對數據的訪問不可避免地需要多個方法調用和指針解引用。小型,固定大小的集合的工廠方法避免了大部分這種開銷,使用緊湊的基於現場或基於陣列的佈局。不需要支持突變(並且在創建時知道收集/地圖大小)也有助於節省空間。

這裡有一些更多的細節:

·這些工廠返回的具體類不會公開為公共API。對於返回的集合/映射的運行時類型或身份不作任何保證,這樣允許實現在不破壞兼容性的情況下隨著時間的推移而改變。調用者唯一可以依賴的是返回的引用是其接口類型的實現。

·生成的對象是可序列化的。序列化代理對象被用作實現類的公共序列化形式。該代理可以防止有關具體實現的信息洩漏到序列化形式中,這樣可以保留將來維護的靈活性,並允許具體實現從發行版更改為發佈,而不會影響序列化兼容性。

·零元素,鍵和值不允許。(沒有最近推出的集合/地圖已經支持null。)此外,禁止空值為更緊湊的內部表示,更快的訪問和更少的特殊情況提供了機會。

·因為這些List實現有望通過索引提供快速元素訪問,它們實現java.util.RandomAccess標記接口。

·存儲在這些集合/地圖中的元素必須支持典型的集合/地圖合同,包括適當的支持hashCode()和equals()。如果a Set或a 的元素以Map影響其hashCode()或equals()方法的方式突變,則收集/映射的行為可能會變得未指定。

·一旦構建並安全地發佈,這些集合/映射實例將可以安全地在多個線程上下文中併發訪問。

結論

Java 9即將推出,開發人員將需要了解其許多增強功能。這一系列的帖子試圖通過探索除廣泛預期的模塊系統和REPL工具之外的各種增強功能來傳達一些知識,這些功能在其他地方得到廣泛的覆蓋。

JEP 269的少量EnumSet工廠方法最大限度地減少Java 9不支持收集文字的痛苦。除了減少語法冗長度,這些工廠方法可以防止收集可變性的任何可能性,因此適用於基於Streams API或其他並行化環境。

相關推薦

推薦中...