Java“双大括号初始化"的效率?
在 Hidden Features of Java 中,最佳答案提到了 双括号初始化,具有非常诱人的语法:
In Hidden Features of Java the top answer mentions Double Brace Initialization, with a very enticing syntax:
Set<String> flavors = new HashSet<String>() {{
add("vanilla");
add("strawberry");
add("chocolate");
add("butter pecan");
}};
这个习惯用法创建了一个匿名内部类,其中只有一个实例初始化器,它可以使用包含范围内的任何 [...] 方法".
This idiom creates an anonymous inner class with just an instance initializer in it, which "can use any [...] methods in the containing scope".
主要问题:这是否像听起来那样低效?它的使用是否应该仅限于一次性初始化?(当然还有炫耀!)
Main question: Is this as inefficient as it sounds? Should its use be limited to one-off initializations? (And of course showing off!)
第二个问题:新的 HashSet 必须是实例初始化程序中使用的this"……有人能解释一下这个机制吗?
Second question: The new HashSet must be the "this" used in the instance initializer ... can anyone shed light on the mechanism?
第三个问题:这个成语是否太晦涩而无法在生产代码中使用?
Third question: Is this idiom too obscure to use in production code?
总结:非常非常好的答案,谢谢大家.关于问题 (3),人们认为语法应该清晰(尽管我建议偶尔发表评论,特别是如果您的代码将传递给可能不熟悉它的开发人员).
Summary: Very, very nice answers, thanks everyone. On question (3), people felt the syntax should be clear (though I'd recommend an occasional comment, especially if your code will pass on to developers who may not be familiar with it).
关于问题 (1),生成的代码应该能够快速运行.额外的 .class 文件确实会导致 jar 文件混乱,并稍微减慢程序启动速度(感谢@coobird 测量).@Thilo 指出垃圾收集可能会受到影响,在某些情况下,额外加载的类的内存成本可能是一个因素.
On question (1), the generated code should run quickly. The extra .class files do cause jar file clutter, and slow program startup slightly (thanks to @coobird for measuring that). @Thilo pointed out that garbage collection can be affected, and the memory cost for the extra loaded classes may be a factor in some cases.
问题 (2) 对我来说是最有趣的.如果我理解答案,DBI 中发生的事情是匿名内部类扩展了由 new 运算符构造的对象的类,因此具有引用正在构造的实例的this"值.非常整洁.
Question (2) turned out to be most interesting to me. If I understand the answers, what's happening in DBI is that the anonymous inner class extends the class of the object being constructed by the new operator, and hence has a "this" value referencing the instance being constructed. Very neat.
总的来说,DBI 给我的印象是一种智力上的好奇心.Coobird 和其他人指出,您可以使用 Arrays.asList、可变参数方法、Google Collections 和建议的 Java 7 Collection 文字来实现相同的效果.Scala、JRuby 和 Groovy 等较新的 JVM 语言也为列表构造提供了简洁的符号,并与 Java 很好地互操作.鉴于 DBI 会使类路径变得混乱,会稍微减慢类加载速度,并使代码更加晦涩难懂,我可能会回避它.但是,我打算向一个刚刚获得 SCJP 并且喜欢关于 Java 语义的善意的较量的朋友提出这个建议!;-) 谢谢大家!
Overall, DBI strikes me as something of an intellectual curiousity. Coobird and others point out you can achieve the same effect with Arrays.asList, varargs methods, Google Collections, and the proposed Java 7 Collection literals. Newer JVM languages like Scala, JRuby, and Groovy also offer concise notations for list construction, and interoperate well with Java. Given that DBI clutters up the classpath, slows down class loading a bit, and makes the code a tad more obscure, I'd probably shy away from it. However, I plan to spring this on a friend who's just gotten his SCJP and loves good natured jousts about Java semantics! ;-) Thanks everyone!
7/2017:Baeldung 对双括号初始化和认为它是一种反模式.
7/2017: Baeldung has a good summary of double brace initialization and considers it an anti-pattern.
12/2017:@Basil Bourque 指出,在新的 Java 9 中,您可以说:
12/2017: @Basil Bourque notes that in the new Java 9 you can say:
Set<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");
这肯定是要走的路.如果您坚持使用早期版本,请查看 Google Collections 的 ImmutableSet.
That's for sure the way to go. If you're stuck with an earlier version, take a look at Google Collections' ImmutableSet.
推荐答案
当我对匿名内部类太着迷时,问题如下:
Here's the problem when I get too carried away with anonymous inner classes:
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
/* snip */
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
/* snip */
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
文件.
These are all classes which were generated when I was making a simple application, and used copious amounts of anonymous inner classes -- each class will be compiled into a separate class
file.
如前所述,双括号初始化"是一个带有实例初始化块的匿名内部类,这意味着为每个初始化"创建一个新类,所有这些通常都是为了制作单个对象.
The "double brace initialization", as already mentioned, is an anonymous inner class with an instance initialization block, which means that a new class is created for each "initialization", all for the purpose of usually making a single object.
考虑到 Java 虚拟机在使用它们时需要读取所有这些类,这可能会导致在 字节码验证进程等.更不用说存储所有这些 class
文件所需的磁盘空间增加了.
Considering that the Java Virtual Machine will need to read all those classes when using them, that can lead to some time in the bytecode verfication process and such. Not to mention the increase in the needed disk space in order to store all those class
files.
似乎在使用双括号初始化时会产生一些开销,因此过度使用它可能不是一个好主意.但正如 Eddie 在评论中指出的那样,无法绝对确定其影响.
It seems as if there is a bit of overhead when utilizing double-brace initialization, so it's probably not such a good idea to go too overboard with it. But as Eddie has noted in the comments, it's not possible to be absolutely sure of the impact.
仅供参考,双括号初始化如下:
Just for reference, double brace initialization is the following:
List<String> list = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
它看起来像 Java 的一个隐藏"特性,但它只是对以下内容的重写:
It looks like a "hidden" feature of Java, but it is just a rewrite of:
List<String> list = new ArrayList<String>() {
// Instance initialization block
{
add("Hello");
add("World!");
}
};
所以它基本上是一个 实例初始化块是匿名内部类的一部分.
So it's basically a instance initialization block that is part of an anonymous inner class.
Joshua Bloch 的 Collection Literals 提案Project Coin 是这样的:
Joshua Bloch's Collection Literals proposal for Project Coin was along the lines of:
List<Integer> intList = [1, 2, 3, 4];
Set<String> strSet = {"Apple", "Banana", "Cactus"};
Map<String, Integer> truthMap = { "answer" : 42 };
遗憾的是,它没有成功 既没有进入 Java 7 也没有进入 8 并且被无限期搁置.
Sadly, it didn't make its way into neither Java 7 nor 8 and was shelved indefinitely.
实验
这是我测试过的简单实验——添加元素 "Hello"
和 "World!"
制作 1000 个 ArrayList
通过 add
方法给他们,使用两种方法:
Here's the simple experiment I've tested -- make 1000 ArrayList
s with the elements "Hello"
and "World!"
added to them via the add
method, using the two methods:
方法一:双大括号初始化
List<String> l = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
方法二:实例化一个ArrayList
和add
List<String> l = new ArrayList<String>();
l.add("Hello");
l.add("World!");
我创建了一个简单的程序来写出一个 Java 源文件来使用两种方法执行 1000 次初始化:
I created a simple program to write out a Java source file to perform 1000 initializations using the two methods:
测试 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!");
}};
/* snip */
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!");
/* snip */
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 毫秒.
Please note, that the elapsed time to initialize the 1000 ArrayList
s and the 1000 anonymous inner classes extending ArrayList
is checked using the System.currentTimeMillis
, so the timer does not have a very high resolution. On my Windows system, the resolution is around 15-16 milliseconds.
两次测试运行 10 次的结果如下:
The results for 10 runs of the two tests were the following:
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 毫秒.
As can be seen, the double brace initialization has a noticeable execution time of around 190 ms.
同时,ArrayList
初始化执行时间为0 ms.当然要考虑定时器的分辨率,不过很有可能在15ms以下.
Meanwhile, the ArrayList
initialization execution time came out to be 0 ms. Of course, the timer resolution should be taken into account, but it is likely to be under 15 ms.
因此,这两种方法的执行时间似乎存在明显差异.看来这两种初始化方法确实有一些开销.
So, there seems to be a noticeable difference in the execution time of the two methods. It does appear that there is indeed some overhead in the two initialization methods.
是的,通过编译Test1
双括号初始化测试程序,生成了1000个.class
文件.
And yes, there were 1000 .class
files generated by compiling the Test1
double brace initialization test program.
相关文章