在 Java 的隐藏功能中,顶部答案提到了双大括号初始化,语法非常诱人:
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 语义的善良角逐的朋友!😉 谢谢大家!
7/2017:Baeldung对双大括号初始化有一个很好的总结,并认为它是一种反模式。
12/2017: @Basil Bourque 指出,在新的 Java 9 中,你可以说:
Set<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");
这肯定是要走的路。如果您坚持使用早期版本,请查看Google Collections的ImmutableSet。
网友回答:
到目前为止尚未指出的此方法的一个属性是,由于您创建了内部类,因此将在其范围内捕获整个包含类。这意味着只要您的 Set 处于活动状态,它就会保留指向包含实例 () 的指针,并防止该实例被垃圾回收,这可能是一个问题。this$0
这一点,以及即使常规 HashSet 可以正常工作(甚至更好)也首先创建一个新类的事实,使我不想使用这种结构(即使我真的很渴望语法糖)。
第二个问题:新的 HashSet 必须是实例初始值设定项中使用的“this”……谁能阐明这种机制?我天真地以为“这个”指的是初始化“口味”的对象。
这就是内部类的工作方式。它们有自己的 ,但它们也有指向父实例的指针,因此您也可以在包含对象上调用方法。如果发生命名冲突,内部类(在您的情况下为 HashSet)优先,但您也可以在“this”前面加上类名以获取外部方法。this
public class Test {
public void add(Object o) {
}
public Set<String> makeSet() {
return new HashSet<String>() {
{
add("hello"); // HashSet
Test.this.add("hello"); // outer instance
}
};
}
}
为了清楚正在创建的匿名子类,您也可以在其中定义方法。例如覆盖HashSet.add()
public Set<String> makeSet() {
return new HashSet<String>() {
{
add("hello"); // not HashSet anymore ...
}
@Override
boolean add(String s){
}
};
}
网友回答:
当我对匿名内部类过于着迷时,这就是问题所在:
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
如前所述,“双大括号初始化”是一个带有实例初始化块的匿名内部类,这意味着为每个“初始化”创建一个新类,所有这些都是为了通常创建一个对象。
考虑到 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的Collection Literals对Project Coin的提案大致如下:
List<Integer> intList = [1, 2, 3, 4];
Set<String> strSet = {"Apple", "Banana", "Cactus"};
Map<String, Integer> truthMap = { "answer" : 42 };
可悲的是,它既没有进入Java 7也没有进入Java 8,并且被无限期地搁置了。
实验
这是我测试过的简单实验 — 使用元素制作 1000 秒,并使用两种方法通过该方法添加到它们中:ArrayList
"Hello"
"World!"
add
方法 1:双大括号初始化
List<String> l = new ArrayList<String>() {{
add("Hello");
add("World!");
}};
方法 2:实例化数组列表
并添加
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 秒和扩展 1000 个匿名内部类的经过时间是使用 ,因此计时器的分辨率不是很高。在我的Windows系统上,分辨率约为15-16毫秒。ArrayList
ArrayList
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 毫秒。
同时,初始化执行时间为 0 毫秒。当然,应该考虑计时器分辨率,但很可能在 15 毫秒以下。ArrayList
因此,这两种方法的执行时间似乎存在明显差异。这两种初始化方法似乎确实存在一些开销。
是的,编译双大括号初始化测试程序生成了 1000 个文件。.class
Test1
网友回答:
每次有人使用双大括号初始化时,都会杀死一只小猫。
除了语法相当不寻常且不是真正的习惯(当然,品味是有争议的)之外,您在应用程序中不必要地创建了两个重大问题,我最近刚刚在博客中更详细地介绍了这些问题。
每次使用双大括号初始化时,都会创建一个新类。例如,这个例子:
Map source = new HashMap(){{
put("firstName", "John");
put("lastName", "Smith");
put("organizations", new HashMap(){{
put("0", new HashMap(){{
put("id", "1234");
}});
put("abc", new HashMap(){{
put("id", "5678");
}});
}});
}};
…将生成以下类:
Test$1$1$1.class
Test$1$1$2.class
Test$1$1.class
Test$1.class
Test.class
对于您的类加载器来说,这是相当多的开销 – 白白!当然,如果您执行一次,则不会花费太多初始化时间。但是,如果您在整个企业应用程序中执行此操作 20’000 次……所有这些堆内存只是为了一点“语法糖”?
如果您采用上述代码并从某个方法返回该映射,则该方法的调用方可能会毫无戒心地保留无法进行垃圾回收的非常繁重的资源。请考虑以下示例:
public class ReallyHeavyObject {
// Just to illustrate...
private int[] tonsOfValues;
private Resource[] tonsOfResources;
// This method almost does nothing
public Map quickHarmlessMethod() {
Map source = new HashMap(){{
put("firstName", "John");
put("lastName", "Smith");
put("organizations", new HashMap(){{
put("0", new HashMap(){{
put("id", "1234");
}});
put("abc", new HashMap(){{
put("id", "5678");
}});
}});
}};
return source;
}
}
返回的现在将包含对 的封闭实例的引用。您可能不想冒险:Map
ReallyHeavyObject
图片来自 http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/
为了回答你的实际问题,人们一直在使用这种语法来假装Java有类似map文字的东西,类似于现有的数组文字:
String[] array = { "John", "Doe" };
Map map = new HashMap() {{ put("John", "Doe"); }};
有些人可能会发现这在语法上很刺激。
模板简介:该模板名称为【Java“双大括号初始化”的效率?】,大小是暂无信息,文档格式为.编程语言,推荐使用Sublime/Dreamweaver/HBuilder打开,作品中的图片,文字等数据均可修改,图片请在作品中选中图片替换即可,文字修改直接点击文字修改即可,您也可以新增或修改作品中的内容,该模板来自用户分享,如有侵权行为请联系网站客服处理。欢迎来懒人模板【Java】栏目查找您需要的精美模板。