千萬不要再這樣建立集合了!極容易記憶體洩露

千萬不要再這樣建立集合了!極容易記憶體洩露

由於Java語言的集合框架中(collections, 如list, map, set等)沒有提供任何簡便的語法結構,這使得在建立常量集合時的工作非常繁瑣。每次建立時我們都要做:

1、定義一個空的集合類變數

2、向這個結合類中逐一新增元素

3、將集合作為引數傳遞給方法

例如,要將一個Set變數傳給一個方法:

Set users = new HashSet();users。add(“Hollis”);users。add(“hollis”);users。add(“HollisChuang”);users。add(“hollis666”);transferUsers(users);

這樣的寫法稍微有些複雜,有沒有簡潔的方式呢?

雙括號語法初始化集合

其實有一個比較簡潔的方式,那就是

雙括號語法

double-brace syntax

)建立並初始化一個新的集合:

public class DoubleBraceTest { public static void main(String[] args) { Set users = new HashSet() {{ add(“Hollis”); add(“hollis”); add(“HollisChuang”); add(“hollis666”); }}; }}

同理,建立並初始化一個HashMap的語法如下:

Map users = new HashMap<>() {{ put(“Hollis”,“Hollis”); put(“hollis”,“hollis”); put(“HollisChuang”,“HollisChuang”);}};

不只是Set、Map,jdk中的集合類都可以用這種方式建立並初始化。

當我們使用這種雙括號語法初始化集合類的時候,在對Java檔案進行編譯時,可以發現一個奇怪的現象,使用javac對DoubleBraceTest進行編譯:

javac DoubleBraceTest。java

我們會發現,得到兩個class檔案:

DoubleBraceTest。classDoubleBraceTest$1。class

有經驗的朋友可能一看到這兩個檔案就會知道,這裡面一定用到了匿名內部類。

沒錯,使用這個雙括號初始化的效果是建立匿名內部類。建立的類有一個隱式的this指標指向外部類。

不建議使用這種形式

首先,使用這種形式建立並初始化集合會導致很多內部類被建立。因為每次使用雙大括號初始化時,都會生成一個新類。如這個例子:

Map hollis = new HashMap(){{ put(“firstName”, “Hollis”); put(“lastName”, “Chuang”); put(“contacts”, new HashMap(){{ put(“0”, new HashMap(){{ put(“blogs”, “http://www。hollischuang。com”); }}); put(“1”, new HashMap(){{ put(“wechat”, “hollischuang”); }}); }});}};

這會使得很多內部類被創建出來:

DoubleBraceTest$1$1$1。classDoubleBraceTest$1$1$2。classDoubleBraceTest$1$1。classDoubleBraceTest$1。classDoubleBraceTest。class

這些內部類被創建出來,是需要被類載入器載入的,這就帶來了一些額外的開銷。

如果您使用上面的程式碼在一個方法中建立並初始化一個map,並從方法返回該map,那麼該方法的呼叫者可能會毫不知情地持有一個無法進行垃圾收集的資源。

public Map getMap() { Map hollis = new HashMap(){{ put(“firstName”, “Hollis”); put(“lastName”, “Chuang”); put(“contacts”, new HashMap(){{ put(“0”, new HashMap(){{ put(“blogs”, “http://www。hollischuang。com”); }}); put(“1”, new HashMap(){{ put(“wechat”, “hollischuang”); }}); }}); }}; return hollis;}

我們嘗試透過呼叫getMap得到這樣一個透過雙括號初始化出來的map

public class DoubleBraceTest { public static void main(String[] args) { DoubleBraceTest doubleBraceTest = new DoubleBraceTest(); Map map = doubleBraceTest。getMap(); }}

返回的Map現在將包含一個對DoubleBraceTest的例項的引用。讀者可以嘗試著透過debug或者以下方式確認這一事實。

Field field = map。getClass()。getDeclaredField(“this$0”);field。setAccessible(true);System。out。println(field。get(map)。getClass());

替代方案

很多人使用雙括號初始化集合,主要是因為他比較方便,可以在定義集合的同時對他進行初始化。

但其實,目前已經有很多方案可以做這個事情了,不需要再使用這種存在風險的方案。

使用Arrays工具類

當我們想要初始化一個List的時候,可以藉助Arrays類,Arrays中提供了asList可以把一個數組轉換成List:

List list2 = Arrays。asList(“hollis ”, “Hollis”, “HollisChuang”);

但是需要注意的是,asList 得到的只是一個 Arrays 的內部類,是一個原來陣列的檢視 List,因此如果對它進行增刪操作會報錯。

使用Stream

Stream是Java中提供的新特性,它可以對傳入流內部的元素進行篩選、排序、聚合等中間操作(intermediate operate),最後由最終操作(terminal operation)得到前面處理的結果。

我們可以藉助Stream來初始化集合:

List list1 = Stream。of(“hollis”, “Hollis”, “HollisChuang”)。collect(Collectors。toList());

使用第三方工具類

很多第三方的集合工具類可以實現這個功能,如Guava等:

ImmutableMap。of(“k1”, “v1”, “k2”, “v2”);ImmutableList。of(“a”, “b”, “c”, “d”);

關於Guava和其中定義的不可變集合,我們在後面會詳細介紹

Java 9內建方法

其實在Java 9 中,在List、Map等集合類中已經內建了初始化的方法,如List中包含了12個過載的of方法,就是來做這個事情的:

/** * Returns an unmodifiable list containing zero elements。 * * See Unmodifiable Lists for details。 * * @param the {@code List}‘s element type * @return an empty {@code List} * * @since 9 */static List of() { return ImmutableCollections。emptyList();}static List of(E e1) { return new ImmutableCollections。List12<>(e1);}static List of(E。。。 elements) { switch (elements。length) { // implicit null check of elements case 0: return ImmutableCollections。emptyList(); case 1: return new ImmutableCollections。List12<>(elements[0]); case 2: return new ImmutableCollections。List12<>(elements[0], elements[1]); default: return new ImmutableCollections。ListN<>(elements); }}