在Java的“ 隐藏功能”中,最常见的答案是Double Brace Initialization
,其语法非常诱人:
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,varargs方法,Google Collections和建议的Java 7 Collection文字可以实现相同的效果。较新的JVM语言(例如Scala,JRuby和Groovy)也为列表构建提供了简洁的符号,并且可以与Java很好地互操作。鉴于DBI会使类路径混乱,使类加载速度变慢,并使代码变得更加晦涩难懂,我可能会回避它。但是,我打算将这个介绍给一个刚刚获得SCJP并且热爱Java语义的好朋友的朋友!;-) 感谢大家!
对双括号初始化有一个很好的总结,并认为它是反模式。
指出在新的Java 9中你可以说:
Set<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");
这是我对匿名内部类太过迷恋的问题:
2019/05/27 16:35 1,602 DemoApp2$1.class
2019/05/27 16:35 1,976 DemoApp2$10.class
2019/05/27 16:35 1,919 DemoApp2$11.class
2019/05/27 16:35 2,404 DemoApp2$12.class
2019/05/27 16:35 1,197 DemoApp2$13.class
/* snip */
2019/05/27 16:35 1,953 DemoApp2$30.class
2019/05/27 16:35 1,910 DemoApp2$31.class
2019/05/27 16:35 2,007 DemoApp2$32.class
2019/05/27 16:35 926 DemoApp2$33$1$1.class
2019/05/27 16:35 4,104 DemoApp2$33$1.class
2019/05/27 16:35 2,849 DemoApp2$33.class
2019/05/27 16:35 926 DemoApp2$34$1$1.class
2019/05/27 16:35 4,234 DemoApp2$34$1.class
2019/05/27 16:35 2,849 DemoApp2$34.class
/* snip */
2019/05/27 16:35 614 DemoApp2$40.class
2019/05/27 16:35 2,344 DemoApp2$5.class
2019/05/27 16:35 1,551 DemoApp2$6.class
2019/05/27 16:35 1,604 DemoApp2$7.class
2019/05/27 16:35 1,809 DemoApp2$8.class
2019/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>() {
// 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和8中,并被无限期搁置。
实验
下面是简单的实验我已经测试-让1000个ArrayLists
的元素"Hello"
,并"World!"
通过加入到他们add
的方法,使用两种方法:
方法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!");
}};
/* 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个ArrayListS
和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 ms。
同时,ArrayList
初始化执行时间为0 ms。当然,应该考虑计时器分辨率,但是很可能在15毫秒以下。
因此,这两种方法的执行时间似乎存在明显差异。看来这两种初始化方法确实存在一些开销。
是的,.class
编译Test1
双括号初始化测试程序生成了1000个文件。
Java中的双大括号初始化语法()是什么?
问题内容: 这些陈述有何不同? 双虚拟= 0; 双虚拟= 0.0; 双虚拟= 0.0d; 双虚拟= 0.0D; 问题答案: 尝试过一个简单的程序(使用0和100来显示“特殊”常数和通用常数之间的差异)之后,Sun Java 6编译器将为1和2输出相同的字节码(情况3和4与2相同)就编译器而言)。 因此,例如: 编译为: 但是,我在Java语言规范中看不到任何能 保证 常量表达式的编译时扩展的东西。
我认为模板函数可以有默认的参数参数(不是模板参数而是运行时参数)。我们也可以用空括号初始化来初始化一个类。但是编译器如何匹配模板呢? 这段代码为什么要编译,编译器如何进行演绎,以及这个函数调用示例中的参数是什么? 我所理解的是:默认括号初始化调用空构造函数,这是隐式创建的,因为没有用户定义的构造函数或用户定义的默认构造函数。也就是说,我们可以用{}初始化任何包。所以扣除不适用于那里,因为我们不能选
问题内容: 我想知道为什么第二个地图声明(使用菱形运算符)在第一个地图声明时不编译。编译错误: 错误:无法推断HashMap的类型参数;Map map2 = new HashMap <>(){原因:不能对K,V是类型变量的匿名内部类使用’<>’:K扩展在HashMap类中声明的对象V扩展在HashMap类中声明的对象 码: 编辑 感谢您的回答- 我应该更好地阅读编译错误。我在JLS中找到了 如果类
问题内容: 我知道您可以在实例化期间初始化数组,如下所示: 有没有办法用ArrayList做同样的事情?还是我必须单独添加内容? 问题答案: Arrays.asList可以在这里提供帮助:
为什么?