Java集合的便利工廠方法

1.概述

Java 9帶來了期待已久的語法糖,它使用簡潔的單行代碼創建了小的不可修改的Collection實例。根據JEP 269 ,JDK 9中將包括新的便利工廠方法。

在本文中,我們將介紹其用法以及實現細節。

2.歷史與動機

使用傳統方式在Java中創建一個小型的不可變Collection非常冗長。

讓我們以Set為例:

Set<String> set = new HashSet<>();

 set.add("foo");

 set.add("bar");

 set.add("baz");

 set = Collections.unmodifiableSet(set);

這對於一個簡單的任務來說太多了代碼,應該可以在一個表達式中完成。

以上對於地圖也適用

但是,對於List ,有一個工廠方法:

List<String> list = Arrays.asList("foo", "bar", "baz");

儘管創建List的方法比構造方法的初始化要好,但這並不那麼明顯,因為通常的直覺是不要在Arrays類中查找創建List的方法:

還有其他降低冗長程度的方法,例如雙括號初始化技術:

Set<String> set = Collections.unmodifiableSet(new HashSet<String>() {{

 add("foo"); add("bar"); add("baz");

 }});

或使用Java 8 Streams

Stream.of("foo", "bar", "baz")

 .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));

雙括號技術僅稍微冗長一些,但大大降低了可讀性(被認為是反模式)。

但是,Java 8版本是單行表達式,它也存在一些問題。首先,它並不明顯和直觀。其次,它仍然很冗長。第三,它涉及不必要對象的創建。第四,該方法不能用於創建Map

總結缺點,以上方法均未處理特定用例,從而產生了一個很小的,不可修改的Collection一流類問題。

3.描述和用法

ListSetMap接口提供了靜態方法,這些方法將元素作為參數並分別返回ListSetMap的實例。

對於所有三個接口,此方法都被稱為of(...)

3.1。清單設定

ListSet工廠方法的簽名和特徵相同:

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

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

方法的用法:

List<String> list = List.of("foo", "bar", "baz");

 Set<String> set = Set.of("foo", "bar", "baz");

如我們所見,它非常簡單,簡短和簡潔。

在示例中,我們使用的方法正好將三個元素作為參數,並返回大小為3的列表/集合

但是,此方法有12個重載版本–十一個參數為0到10,而一個參數為var-args:

static <E> List<E> of()

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

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

 // ....and so on



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

對於大多數實際目的,10個元素就足夠了,但是如果需要更多元素,則可以使用var-args版本。

現在,我們可能會問,如果有一個適用於任意數量元素的var-args版本,那麼擁有11個額外方法的意義何在?

答案就是性能。每個var-args方法調用都會隱式創建一個數組。使用重載的方法可以避免不必要的對象創建及其垃圾回收開銷。相反, Arrays.asList始終創建該隱式數組,因此,當元素數較少時,效率較低。

在使用工廠方法創建Set的過程中,如果將重複的元素作為參數傳遞,則在運行時會拋出IllegalArgumentException

@Test(expected = IllegalArgumentException.class)

 public void onDuplicateElem_IfIllegalArgExp_thenSuccess() {

 Set.of("foo", "bar", "baz", "foo");

 }

這裡要注意的重要一點是,由於工廠方法使用泛型,因此原始類型會自動裝箱。

如果傳遞原始類型的數組,則返回該原始類型的數組列表

例如:

int[] arr = { 1, 2, 3, 4, 5 };

 List<int[]> list = List.of(arr);

在這種情況下,將返回大小為1的List <int []> ,並且索引0處的元素包含該數組。

3.2。地圖

Map工廠方法的簽名為:

static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3)

和用法:

Map<String, String> map = Map.of("foo", "a", "bar", "b", "baz", "c");

ListSet相似, of(...)方法被重載為具有0到10個鍵值對。

對於Map ,對於10個以上的鍵/值對有一種不同的方法:

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

它的用法:

Map<String, String> map = Map.ofEntries(

 new AbstractMap.SimpleEntry<>("foo", "a"),

 new AbstractMap.SimpleEntry<>("bar", "b"),

 new AbstractMap.SimpleEntry<>("baz", "c"));

為Key傳遞重複值將拋出IllegalArgumentException

@Test(expected = IllegalArgumentException.class)

 public void givenDuplicateKeys_ifIllegalArgExp_thenSuccess() {

 Map.of("foo", "a", "foo", "b");

 }

同樣,在Map的情況下,原始類型也會自動裝箱。

4.實施說明

使用工廠方法創建的集合不是常用的實現。

例如, List不是ArrayList ,而Map不是HashMap 。這些是Java 9中引入的不同實現。這些實現是內部的,其構造函數具有受限制的訪問權限。

在本節中,我們將看到一些重要的實現差異,這是所有三種類型的集合所共有的。

4.1。一成不變的

使用工廠方法創建的集合是不可變的,並且更改元素,添加新元素或刪除元素會拋出UnsupportedOperationException

@Test(expected = UnsupportedOperationException.class)

 public void onElemAdd_ifUnSupportedOpExpnThrown_thenSuccess() {

 Set<String> set = Set.of("foo", "bar");

 set.add("baz");

 }
@Test(expected = UnsupportedOperationException.class)

 public void onElemModify_ifUnSupportedOpExpnThrown_thenSuccess() {

 List<String> list = List.of("foo", "bar");

 list.set(0, "baz");

 }

另一方面,從Arrays.asList返回的集合 是可變的。因此,可以更改或刪除現有元素。類似List.of,我們不能將新元素添加到從Arrays.asList返回一個列表。

@Test(expected = UnsupportedOperationException.class)

 public void onElemRemove_ifUnSupportedOpExpnThrown_thenSuccess() {

 Map<String, String> map = Map.of("foo", "a", "bar", "b");

 map.remove("foo");

 }

4.2。不允許元素

對於ListSet ,任何元素都不能為null 。對於Map ,鍵或值都不能為null 。傳遞null參數會引發NullPointerException

@Test(expected = NullPointerException.class)

 public void onNullElem_ifNullPtrExpnThrown_thenSuccess() {

 List.of("foo", "bar", null);

 }

List.of相反Arrays.asList方法接受值。

4.3。基於價值的實例

通過工廠方法創建的實例是基於值的。這意味著工廠可以自由創建新實例或返回現有實例。

因此,如果我們創建具有相同值的List,則它們可能會或可能不會引用堆上的同一對象:

List<String> list1 = List.of("foo", "bar");

 List<String> list2 = List.of("foo", "bar");

在這種情況下,取決於JVM, list1 == list2可能會或可能不會為true

4.4。序列化

如果集合的元素是可序列化的,則從工廠方法創建的集合是可序列化的。

5.結論

在本文中,我們介紹了Java 9中引入的Collections的新工廠方法。

通過回顧一些過去創建不可修改集合的方法,我們總結了為什麼此功能是受歡迎的更改。我們介紹了它的用法,並突出顯示了在使用它們時要考慮的關鍵點。

最後,我們澄清了這些集合與常用的實現方式不同,並指出了關鍵的區別。

GitHub上提供本文的完整源代碼和單元測試。