最佳答案提到了[Double BraceInitialization,其语法 :
Set<String> flavors = new HashSet<String>() {{ add("vanilla"); add("strawberry"); add("chocolate"); add("butter pecan"); }};
这个习惯用法创建了一个匿名内部类,其中只有一个实例初始化程序,它“可以使用包含范围内的任何 […] 方法”。
主要问题:这是否像听起来那样 低效? 它的使用是否应该仅限于一次性初始化?(当然还有炫耀!)
第二个问题:新的 HashSet 必须是实例初始化程序中使用的“this”……有人能解释一下这个机制吗?
第三个问题:这个成语是否太 晦涩难于 在生产代码中使用?
摘要: 非常非常好的答案,谢谢大家。关于问题 (3),人们认为语法应该清晰(尽管我建议偶尔发表评论,特别是如果您的代码将传递给可能不熟悉它的开发人员)。
关于问题(1),生成的代码应该运行得很快。额外的 .class 文件确实会导致 jar 文件混乱,并稍微减慢程序启动速度(感谢@coobird 测量)。@Thilo 指出垃圾收集可能会受到影响,并且在某些情况下,额外加载的类的内存成本可能是一个因素。
问题(2)对我来说是最有趣的。如果我理解答案,DBI 中发生的事情是匿名内部类扩展了由 new 运算符构造的对象的类,因此具有引用正在构造的实例的“this”值。非常整洁。
总的来说,DBI 给我的印象是一种智力上的好奇心。Coobird 和其他人指出,您可以使用 Arrays.asList、可变参数方法、Google Collections 和建议的 Java 7 Collection 文字来实现相同的效果。Scala、JRuby 和 Groovy 等较新的 JVM 语言也为列表构造提供了简洁的符号,并与 Java 很好地互操作。鉴于 DBI 会使类路径变得混乱,会稍微减慢类加载速度,并使代码更加晦涩难懂,我可能会回避它。但是,我打算向一个刚拿到 SCJP 并且喜欢关于 Java 语义的善意的较量的朋友推荐这个!;-) 感谢大家!
7/2017:Baeldung对双括号初始化有一个很好的总结,并认为它是一种反模式。
12/2017:@Basil Bourque 指出,在新的 Java 9 中,您可以说:
Set<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");
这肯定是要走的路。如果您对早期版本感到困惑,请查看Google Collections 的 ImmutableSet。
当我对匿名内部类太着迷时,这就是问题所在:
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文件中。
class
正如已经提到的,“双括号初始化”是一个带有实例初始化块的匿名内部类,这意味着为每个“初始化”创建一个新类,所有这些通常都是为了制作单个对象。
考虑到 Java 虚拟机在使用它们时需要读取所有这些类,这可能会导致字节码验证过程等一些时间。更不用说增加存储所有这些class文件所需的磁盘空间了。
使用双大括号初始化时似乎有一些开销,所以过分使用它可能不是一个好主意。但正如埃迪在评论中指出的那样,不可能绝对确定影响。
仅供参考,双括号初始化如下:
List<String> list = new ArrayList<String>() {{ add("Hello"); add("World!"); }};
它看起来像是 Java 的“隐藏”功能,但它只是对以下内容的重写:
List<String> list = new ArrayList<String>() { // Instance initialization block { add("Hello"); add("World!"); } };
所以它基本上是一个实例初始化块,它是匿名内部类的一部分。
Joshua Bloch对Project Coin的Collection Literals 提案大致如下:
List<Integer> intList = [1, 2, 3, 4]; Set<String> strSet = {"Apple", "Banana", "Cactus"}; Map<String, Integer> truthMap = { "answer" : 42 };
遗憾的是,它既没有进入 Java 7 也没有进入 Java 8,因此被无限期搁置。
实验
这是我测试过的简单实验——ArrayList使用元素制作 1000 秒"Hello"并"World!"通过方法添加到它们add,使用两种方法:
ArrayList
"Hello"
"World!"
add
方法一:双括号初始化
List<String> l = new ArrayList<String>() {{ add("Hello"); add("World!"); }};
方法2:实例化一个ArrayListandadd
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!"); }}; /* 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); } }
请注意,初始化 1000ArrayList秒和 1000 个匿名内部类扩展ArrayList所用的时间是使用 来检查的System.currentTimeMillis,因此计时器没有非常高的分辨率。在我的 Windows 系统上,分辨率约为 15-16 毫秒。
System.currentTimeMillis
两次测试运行 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 ms。当然,要考虑定时器的分辨率,但它很可能在 15 毫秒以下。
因此,这两种方法的执行时间似乎存在显着差异。看来这两种初始化方法确实存在一些开销。
是的,.class编译Test1双括号初始化测试程序生成了 1000 个文件。
.class
Test1