当我过于沉迷于匿名内部类时,会遇到一个问题:
2009/05/27 16:35 1,602 DemoApp2$1.class
2009/05/27 16:35 1,976 DemoApp2$10.class
2009/05/27 16:35 1,919 DemoApp2$11.class
2009/05/27 16:35 2,404 DemoApp2$12.class
2009/05/27 16:35 1,197 DemoApp2$13.class
2009/05/27 16:35 1,953 DemoApp2$30.class
2009/05/27 16:35 1,910 DemoApp2$31.class
2009/05/27 16:35 2,007 DemoApp2$32.class
2009/05/27 16:35 926 DemoApp2$33$1$1.class
2009/05/27 16:35 4,104 DemoApp2$33$1.class
2009/05/27 16:35 2,849 DemoApp2$33.class
2009/05/27 16:35 926 DemoApp2$34$1$1.class
2009/05/27 16:35 4,234 DemoApp2$34$1.class
2009/05/27 16:35 2,849 DemoApp2$34.class
2009/05/27 16:35 614 DemoApp2$40.class
2009/05/27 16:35 2,344 DemoApp2$5.class
2009/05/27 16:35 1,551 DemoApp2$6.class
2009/05/27 16:35 1,604 DemoApp2$7.class
2009/05/27 16:35 1,809 DemoApp2$8.class
2009/05/27 16:35 2,022 DemoApp2$9.class
这些都是在我制作一个简单应用程序时生成的类,使用了大量的匿名内部类——每个类将编译成一个单独的
class
文件。
"双括号初始化",如前所述,是一个带有实例初始化块的匿名内部类,这意味着每个"初始化"都会创建一个新类,通常是为了生成单个对象。
考虑到Java虚拟机在使用它们时需要读取所有这些类,这可能会导致
字节码验证过程等方面的一些时间。更不用说增加存储所有这些
class
文件所需的磁盘空间。
使用双括号初始化似乎会带来一些开销,因此过度使用可能不是一个好主意。但正如Eddie在评论中指出的那样,无法绝对确定其影响。
仅供参考,双括号初始化如下:
List<String> list = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
它看起来像是Java的一个“隐藏”功能,但实际上它只是以下代码的重写:
List<String> list = new ArrayList<String>() {
{
add("Hello");
add("World!");
}
};
所以它基本上是 实例初始化块 的一部分,属于一个 匿名内部类。
Joshua Bloch提出的集合字面值建议属于Project Coin,大致如下:
List<Integer> intList = [1, 2, 3, 4];
Set<String> strSet = {"Apple", "Banana", "Cactus"};
Map<String, Integer> truthMap = { "answer" : 42 };
可悲的是,它未能进入Java 7或8,并被无限期搁置。
实验
这是我测试过的简单实验 - 使用两种方法,通过add
方法将元素"Hello"
和"World!"
添加到1000个ArrayList
中:
方法1:双括号初始化
List<String> l = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
方法2:实例化一个ArrayList
并使用add
方法添加元素
List<String> l = new ArrayList<String>();
l.add("Hello");
l.add("World!");
我创建了一个简单的程序,用于编写Java源文件,使用两种方法之一执行1000次初始化:
测试1:
class Test1 {
public static void main(String[] s) {
long st = System.currentTimeMillis();
List<String> l0 = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
List<String> l1 = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
List<String> l999 = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
System.out.println(System.currentTimeMillis() - st);
}
}
测试2:
class Test2 {
public static void main(String[] s) {
long st = System.currentTimeMillis();
List<String> l0 = new ArrayList<String>();
l0.add("Hello");
l0.add("World!");
List<String> l1 = new ArrayList<String>();
l1.add("Hello");
l1.add("World!");
List<String> l999 = new ArrayList<String>();
l999.add("Hello");
l999.add("World!");
System.out.println(System.currentTimeMillis() - st);
}
}
请注意,初始化1000个
ArrayList
和1000个扩展
ArrayList
的匿名内部类的经过时间是使用
System.currentTimeMillis
检查的,因此计时器的分辨率不是很高。在我的Windows系统上,分辨率约为15-16毫秒。
两个测试的10次运行结果如下:
Test1 Times (ms) Test2 Times (ms)
---------------- ----------------
187 0
203 0
203 0
188 0
188 0
187 0
203 0
188 0
188 0
203 0
可以看出,双括号初始化的执行时间约为190毫秒。
与此同时,ArrayList
初始化的执行时间为0毫秒。当然,应该考虑计时器的分辨率,但很可能是在15毫秒以下。
因此,两种方法的执行时间存在明显差异。似乎确实存在一些初始化方法的开销。
是的,通过编译Test1
双括号初始化测试程序生成了1000个.class
文件。
flavors
是一个HashSet
,但实际上它是一个匿名子类。 - Elazar LeibovichSet<String> flavors = Set.of( "vanilla" , "strawberry" , "chocolate" , "butter pecan" ) ;
- Basil Bourque