当前位置: 首页 > 科技观察

永远不要再创建这样的集合!非常容易泄漏内存!

时间:2023-03-18 02:01:24 科技观察

由于Java语言的集合框架(集合,如list、map、set等)没有提供任何方便的语法结构,这使得创建常量集合时的工作非常繁琐。每次创建,我们要做:1.定义一个空的集合类变量2.将元素一个一个添加到这个组合类中3.将集合作为参数传递给方法。例如,将Set变量传递给方法:Setusers=newHashSet();users.add("Hollis");users.add("hollis");users.add("HollisChuang");users.add("hollis666");transferUsers(用户);有点复杂,有没有简洁的方法?用双大括号语法初始化集合其实还有更简单的方法,即双大括号语法(double-bracesyntax)创建并初始化一个新的集合:publicclassDoubleBraceTest{publicstaticvoidmain(String[]args){Setusers=newHashSet(){{add("Hollis");add("hollis");add("HollisChuang");add("hollis666");}};}}同理,创建并初始化一个HashMap,语法如下:Mapusers=newHashMap<>(){{put("Hollis","Hollis");put("hollis","hollis");put("HollisChuang","HollisChuang");}};不仅是Set,Map,jdk中的集合类也可以这样创建和初始化。当我们使用这种双括号语法来初始化集合类时,我们在编译Java文件时会发现一个奇怪的现象。使用javac编译DoubleBraceTest:javacDoubleBraceTest.java我们会发现得到了两个类文件:DoubleBraceTest.classDoubleBraceTest$1.class有经验的朋友看到??这两个文件可能就知道必须使用匿名内部类了。没错,使用这个双括号初始化的效果就是创建了一个匿名内部类。创建的类有一个隐式的this指针指向外部类。不推荐这种形式。首先,使用这种形式创建和初始化一个集合会导致创建很多内部类。因为每次用双花括号初始化,都会生成一个新的类。比如这个例子:Maphollis=newHashMap(){{put("firstName","Hollis");put("lastName","Chuang");put("contacts",newHashMap(){{put("0",newHashMap(){{put("博客","http://www.hollishuang.com");}});put("1",newHashMap(){{put("微信","hollishuang");}});}});}};这会导致创建很多内部类:DoubleBraceTest$1$1$1.classDoubleBraceTest$1$1$2.classDoubleBraceTest$1$1.classDoubleBraceTest$1.classDoubleBraceTest.class创建这些内部类,需要通过类加载器加载,这就带来了一些额外的开销。如果您使用上面的代码在一个方法中创建和初始化一个映射,并从该方法返回该映射,该方法的调用者可能会在不知不觉中持有无法被垃圾回收的资源。publicMapgetMap(){Maphollis=newHashMap(){{put("firstName","Hollis");put("lastName","Chuang");put("联系人",newHashMap(){{put("0",newHashMap(){{put("博客","http://www.hollishuang.com");}});put("1",newHashMap(){{put("微信","hollishuang");}});}});}};returnhollis;}我们尝试通过调用getMap{publicstaticvoidmain(String[]args){DoubleBraceTestdoubleBraceTest=newDoubleBraceTest();Mapmap=doubleBraceTest.getMap();}}返回的Map现在将包含对DoubleBraceTest实例的引用。读者可以通过调试或以下方式尝试确认这一事实。Fieldfield=map.getClass().getDeclaredField("this$0");field.setAccessible(true);System.out.println(field.get(map).getClass());Alternative很多人用双括号来初始化集合,主要是因为他比较方便,可以边定义集合边初始化。但实际上,已经有很多解决方案可以做到这一点,没有必要使用这种有风险的方案。使用Arrays工具类当我们想要初始化一个List时,我们可以使用Arrays类。Arrays提供asList将数组转换为List:Listlist2=Arrays.asList("hollis","Hollis","HollisChuang");但是需要注意的是,asList获取的只是Arrays的一个内部类,是一个原始数组的视图List,所以如果添加或者删除,都会报错。使用StreamStream是Java中提供的一个新特性。它可以对传入流内部的元素进行筛选、排序、聚合等中间操作,最终通过最终操作得到前面处理的结果。我们可以借助Stream来初始化集合:javaListlist1=Stream.of("hollis","Hollis","HollisChuang").collect(Collectors.toList());使用第三方工具采集很多第三方采集工具类可以实现这个功能,比如Guava等:ImmutableMap.of("k1","v1","k2","v2");ImmutableList.of(“ABCD”);关于Guava以及其中定义的不可变集合,我们会在第19章详细介绍Java9的内置方法。事实上,在Java9中,List、Map等集合类中已经内置了初始化方法,比如列表。包含12个重载的方法,只是为了这样做:/***返回一个包含零元素的不可修改列表。**参见不可修改列表的详细信息。**@paramthe{@codeList}的元素类型*@returnanempty{@codeList}**@since9*/staticListof(){returnImmutableCollections.emptyList();}staticListof(Ee1){returnnewImmutableCollections.List12<>(e1);}staticListof(E...elements){switch(elements.length){//implicitnullcheckofelementscase0:returnImmutableCollections.emptyList();case1:returnnewImmutableCollections.List12<>(elements[0]);case2:returnnewImmutableCollections.List12<>(elements[0],elements[1]);default:returnnewImmutableCollections.ListN<>(elements);}}