首页 > 应用开发 > Java > 千万不要再这样创建集合了!极容易内存泄露!
由于Java语言的集合框架中(collections, 如list, map, set等)没有提供任何简便的语法结构,这使得在建立常量集合时的工作非常繁索。每次建立时我们都要做:
1、定义一个空的集合类变量 2、向这个结合类中逐一添加元素 3、将集合做为参数传递给方法
例如,要将一个Set变量传给一个方法:
Map
<String
,String
> users
=
new
HashMap
<
>
(
)
{
{
put
(
"Hollis"
,
"Hollis"
)
;
put
(
"hollis"
,
"hollis"
)
;
put
(
"HollisChuang"
,
"HollisChuang"
)
;
}
}
;
这样的写法稍微有些复杂,有没有简洁的方式呢?
其实有一个比较简洁的方式,那就是
双括号语法(
double-brace syntax)建立并初始化一个新的集合:
javac DoubleBraceTest.java
同理,创建并初始化一个HashMap的语法如下:
DoubleBraceTest
.class
DoubleBraceTest$
1.class
不只是Set、Map,jdk中的集合类都可以用这种方式创建并初始化。
当我们使用这种双括号语法初始化集合类的时候,在对Java文件进行编译时,可以发现一个奇怪的现象,使用javac对DoubleBraceTest进行编译:
javac DoubleBraceTest.java
我们会发现,得到两个class文件:
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"
)
;
}
}
)
;
}
}
)
;
}
}
;
有经验的朋友可能一看到这两个文件就会知道,这里面一定用到了匿名内部类。
没错,使用这个双括号初始化的效果是创建匿名内部类。创建的类有一个隐式的this指针指向外部类。
首先,使用这种形式创建并初始化集合会导致很多内部类被创建。因为每次使用双大括号初始化时,都会生成一个新类。如这个例子:
DoubleBraceTest$
1$
1$
1.class
DoubleBraceTest$
1$
1$
2.class
DoubleBraceTest$
1$
1.class
DoubleBraceTest$
1.class
DoubleBraceTest
.class
这会使得很多内部类被创建出来:
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
;
}
这些内部类被创建出来,是需要被类加载器加载的,这就带来了一些额外的开销。
如果您使用上面的代码在一个方法中创建并初始化一个map,并从方法返回该map,那么该方法的调用者可能会毫不知情地持有一个无法进行垃圾收集的资源。
public
class
DoubleBraceTest
{
public
static
void
main
(
String
[
] args
)
{
DoubleBraceTest doubleBraceTest
=
new
DoubleBraceTest
(
)
;
Map map
= doubleBraceTest
.
getMap
(
)
;
}
}
我们尝试通过调用getMap得到这样一个通过双括号初始化出来的map
Field field
= map
.
getClass
(
)
.
getDeclaredField
(
"this$0"
)
;
field
.
setAccessible
(
true
)
;
System
.out
.
println
(field
.
get
(map
)
.
getClass
(
)
)
;
返回的Map现在将包含一个对DoubleBraceTest的实例的引用。读者可以尝试这通过debug或者以下方式确认这一事实。
List<String> list2 = Arrays.asList("hollis ", "Hollis", "HollisChuang");
替代方案
很多人使用双括号初始化集合,主要是因为他比较方便,可以在定义集合的同时对他进行初始化。
但其实,目前已经有很多方案可以做这个事情了,不需要再使用这种存在风险的方案。
当我们想要初始化一个List的时候,可以借助Arrays类,Arrays中提供了asList可以把一个数组转换成List:
List<String> list1 = Stream.of("hollis", "Hollis", "HollisChuang").collect(Collectors.toList());
但是需要注意的是,asList 得到的只是一个 Arrays 的内部类,是一个原来数组的视图 List,因此如果对它进行增删操作会报错。
Stream是Java中提供的新特性,他可以对传入流内部的元素进行筛选、排序、聚合等中间操作(intermediate operate),最后由最终操作(terminal operation)得到前面处理的结果。
我们可以借助Stream来初始化集合:
ImmutableMap
.
of
(
"k1"
,
"v1"
,
"k2"
,
"v2"
)
;
ImmutableList
.
of
(
"a"
,
"b"
,
"c"
,
"d"
)
;
使用第三方工具类
很多第三方的集合工具类可以实现这个功能,如Guava等:
ImmutableMap
.
of
(
"k1"
,
"v1"
,
"k2"
,
"v2"
)
;
ImmutableList
.
of
(
"a"
,
"b"
,
"c"
,
"d"
)
;
关于Guava和其中定义的不可变集合,我们在后面会详细介绍
其实在Java 9 中,在List、Map等集合类中已经内置了初始化的方法,如List中包含了12个重载的of方法,就是来做这个事情的:
/**
* Returns an unmodifiable list containing zero elements.
* java学习交流:737251827 进入可领取学习资源及对十年开发经验大佬提问,免费解答!
* 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
)
;
}
}
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70010294/viewspace-2847637/ ,如需转载,请注明出处,否则将追究法律责任。