千萬不要再這樣創(chuàng)建集合了!極容易內(nèi)存泄露!
由于Java語言的集合框架中(collections, 如list, map, set等)沒有提供任何簡便的語法結(jié)構(gòu),這使得在建立常量集合時(shí)的工作非常繁索。每次建立時(shí)我們都要做:
1、定義一個(gè)空的集合類變量
2、向這個(gè)結(jié)合類中逐一添加元素
3、將集合做為參數(shù)傳遞給方法
例如,要將一個(gè)Set變量傳給一個(gè)方法:
- Set users = new HashSet();
- users.add("Hollis");
- users.add("hollis");
- users.add("HollisChuang");
- users.add("hollis666");
- transferUsers(users);
這樣的寫法稍微有些復(fù)雜,有沒有簡潔的方式呢?
雙括號(hào)語法初始化集合
其實(shí)有一個(gè)比較簡潔的方式,那就是雙括號(hào)語法(double-brace syntax)建立并初始化一個(gè)新的集合:
- public class DoubleBraceTest {
- public static void main(String[] args) {
- Set users = new HashSet() {{
- add("Hollis");
- add("hollis");
- add("HollisChuang");
- add("hollis666");
- }};
- }
- }
同理,創(chuàng)建并初始化一個(gè)HashMap的語法如下:
- Map<String,String> users = new HashMap<>() {{
- put("Hollis","Hollis");
- put("hollis","hollis");
- put("HollisChuang","HollisChuang");
- }};
不只是Set、Map,jdk中的集合類都可以用這種方式創(chuàng)建并初始化。
當(dāng)我們使用這種雙括號(hào)語法初始化集合類的時(shí)候,在對Java文件進(jìn)行編譯時(shí),可以發(fā)現(xiàn)一個(gè)奇怪的現(xiàn)象,使用javac對DoubleBraceTest進(jìn)行編譯:
- javac DoubleBraceTest.java
我們會(huì)發(fā)現(xiàn),得到兩個(gè)class文件:
- DoubleBraceTest.class
- DoubleBraceTest$1.class
有經(jīng)驗(yàn)的朋友可能一看到這兩個(gè)文件就會(huì)知道,這里面一定用到了匿名內(nèi)部類。
沒錯(cuò),使用這個(gè)雙括號(hào)初始化的效果是創(chuàng)建匿名內(nèi)部類。創(chuàng)建的類有一個(gè)隱式的this指針指向外部類。
不建議使用這種形式
首先,使用這種形式創(chuàng)建并初始化集合會(huì)導(dǎo)致很多內(nèi)部類被創(chuàng)建。因?yàn)槊看问褂秒p大括號(hào)初始化時(shí),都會(huì)生成一個(gè)新類。如這個(gè)例子:
- 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");
- }});
- }});
- }};
這會(huì)使得很多內(nèi)部類被創(chuàng)建出來:
- DoubleBraceTest$1$1$1.class
- DoubleBraceTest$1$1$2.class
- DoubleBraceTest$1$1.class
- DoubleBraceTest$1.class
- DoubleBraceTest.class
這些內(nèi)部類被創(chuàng)建出來,是需要被類加載器加載的,這就帶來了一些額外的開銷。
如果您使用上面的代碼在一個(gè)方法中創(chuàng)建并初始化一個(gè)map,并從方法返回該map,那么該方法的調(diào)用者可能會(huì)毫不知情地持有一個(gè)無法進(jìn)行垃圾收集的資源。
- 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;
- }
我們嘗試通過調(diào)用getMap得到這樣一個(gè)通過雙括號(hào)初始化出來的map
- public class DoubleBraceTest {
- public static void main(String[] args) {
- DoubleBraceTest doubleBraceTest = new DoubleBraceTest();
- Map map = doubleBraceTest.getMap();
- }
- }
返回的Map現(xiàn)在將包含一個(gè)對DoubleBraceTest的實(shí)例的引用。讀者可以嘗試這通過debug或者以下方式確認(rèn)這一事實(shí)。
- Field field = map.getClass().getDeclaredField("this$0");
- field.setAccessible(true);
- System.out.println(field.get(map).getClass());
替代方案
很多人使用雙括號(hào)初始化集合,主要是因?yàn)樗容^方便,可以在定義集合的同時(shí)對他進(jìn)行初始化。
但其實(shí),目前已經(jīng)有很多方案可以做這個(gè)事情了,不需要再使用這種存在風(fēng)險(xiǎn)的方案。
使用Arrays工具類
當(dāng)我們想要初始化一個(gè)List的時(shí)候,可以借助Arrays類,Arrays中提供了asList可以把一個(gè)數(shù)組轉(zhuǎn)換成List:
- List<String> list2 = Arrays.asList("hollis ", "Hollis", "HollisChuang");
但是需要注意的是,asList 得到的只是一個(gè) Arrays 的內(nèi)部類,是一個(gè)原來數(shù)組的視圖 List,因此如果對它進(jìn)行增刪操作會(huì)報(bào)錯(cuò)。
使用Stream
Stream是Java中提供的新特性,他可以對傳入流內(nèi)部的元素進(jìn)行篩選、排序、聚合等中間操作(intermediate operate),最后由最終操作(terminal operation)得到前面處理的結(jié)果。
我們可以借助Stream來初始化集合:java
- List<String> list1 = Stream.of("hollis", "Hollis", "HollisChuang").collect(Collectors.toList());
使用第三方工具類
很多第三方的集合工具類可以實(shí)現(xiàn)這個(gè)功能,如Guava等:
- ImmutableMap.of("k1", "v1", "k2", "v2");
- ImmutableList.of("a", "b", "c", "d");
關(guān)于Guava和其中定義的不可變集合,我們在第19章節(jié)中會(huì)詳細(xì)介紹
Java 9內(nèi)置方法
其實(shí)在Java 9 中,在List、Map等集合類中已經(jīng)內(nèi)置了初始化的方法,如List中包含了12個(gè)重載的of方法,就是來做這個(gè)事情的:
- /**
- * Returns an unmodifiable list containing zero elements.
- *
- * See <a href="#unmodifiable">Unmodifiable Lists</a> for details.
- *
- * @param <E> the {@code List}'s element type
- * @return an empty {@code List}
- *
- * @since 9
- */
- static <E> List<E> of() {
- return ImmutableCollections.emptyList();
- }
- static <E> List<E> of(E e1) {
- return new ImmutableCollections.List12<>(e1);
- }
- static <E> List<E> 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);
- }
- }