什么是 Java 中的双大括号初始化?
Posted
技术标签:
【中文标题】什么是 Java 中的双大括号初始化?【英文标题】:What is Double Brace initialization in Java? 【发布时间】:2010-12-29 20:10:51 【问题描述】:什么是 Java 中的双大括号初始化语法 ( ...
)?
【问题讨论】:
见***.com/questions/1372113/… 另见***.com/q/924285/45935 双括号初始化是一个非常危险的特性,应该谨慎使用。它可能会破坏 equals 契约并引入棘手的内存泄漏。 This文章详细介绍。 Andrii 发布的链接失效了,不过我自己写了一篇博文:Don't use the double-brace initialization trick 【参考方案1】:双大括号初始化创建一个从指定类(外大括号)派生的匿名类,并在该类中提供一个初始化块(内大括号)。例如
new ArrayList<Integer>()
add(1);
add(2);
;
请注意,使用这种双括号初始化的效果是您正在创建匿名内部类。创建的类有一个隐含的this
指向周围外部类的指针。虽然通常不是问题,但在某些情况下可能会导致悲伤,例如在序列化或垃圾收集时,请注意这一点。
【讨论】:
感谢您阐明内大括号和外大括号的含义。我想知道为什么突然允许两个具有特殊含义的大括号,而实际上它们是普通的 java 构造,只是作为一些神奇的新技巧出现。不过,类似的事情让我质疑 Java 语法。如果您还不是专家,那么阅读和写作可能会非常棘手。 这样的“魔术语法”存在于许多语言中,例如几乎所有类 C 语言都支持 for 循环中“x --> 0”的“goes to 0”语法,即“ x-- > 0" 具有奇怪的空间位置。 我们可以得出结论,“双括号初始化”本身并不存在,它只是创建一个匿名类和一个初始化块,一旦组合起来,看起来就像一个语法结构,但实际上并非如此。 谢谢!由于匿名内部类的使用,当我们用双括号初始化序列化某些东西时,Gson 返回 null。【参考方案2】:每次有人使用双括号初始化,都会杀死一只小猫。
除了语法相当不寻常且不是真正地道(当然,品味值得商榷)之外,您还不必要地在您的应用程序中产生了两个重大问题,which I've just recently blogged about in more detail here。
1。你创建的匿名类太多了
每次使用双括号初始化时,都会创建一个新类。例如。这个例子:
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 次...所有这些堆内存只是为了一点“语法糖”?
2。您可能会造成内存泄漏!
如果您采用上述代码并从方法返回该映射,则该方法的调用者可能会毫无防备地持有无法被垃圾收集的非常重的资源。考虑以下示例:
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/
3。你可以假装 Java 有映射字面量
为了回答你的实际问题,人们一直在使用这种语法来假装 Java 有类似映射文字的东西,类似于现有的数组文字:
String[] array = "John", "Doe" ;
Map map = new HashMap() put("John", "Doe"); ;
有些人可能会觉得这在句法上很刺激。
【讨论】:
“你创建的匿名类太多了”——看看(比如说)Scala 如何创建匿名类,我不太确定这是一个主要问题 这不是声明静态地图的有效且好方法吗?如果一个 HashMap 用...
初始化并声明为static
字段,那么应该不会有任何可能的内存泄漏,只有一个匿名类,没有封闭的实例引用,对吧?
@lorenzo-s:是的,2) 和 3) 不适用,只有 1)。幸运的是,在 Java 9 中,终于有 Map.of()
用于此目的,所以这将是一个更好的解决方案
可能值得注意的是,内部地图也引用了外部地图,因此间接引用了ReallyHeavyObject
。此外,匿名内部类捕获类主体中使用的所有局部变量,因此如果您不仅使用常量来初始化具有此模式的集合或映射,则内部类实例将捕获所有它们并继续引用它们,即使实际从集合或地图。因此,在这种情况下,这些实例不仅需要两倍于引用所需的内存,而且在这方面还有另一个内存泄漏。
@JacobEckel 好吧,我们有 2021 年,Java 有足够接近的东西来映射文字,以保留这个答案的例子:Map source = Map.of("firstName", "John", "lastName", "Smith", "organizations", Map.of("0", Map.of("id", "1234"), "abc", Map.of("id", "5678")))
(从 Java 9 开始),它确实产生了一个不可变的映射。跨度>
【参考方案3】:
第一个大括号创建一个新的匿名内部类。
第二组大括号在 Class 中创建一个实例初始化器,如静态块。
例如:
public class TestHashMap
public static void main(String[] args)
HashMap<String,String> map = new HashMap<String,String>()
put("1", "ONE");
put("2", "TWO");
put("3", "THREE");
;
Set<String> keySet = map.keySet();
for (String string : keySet)
System.out.println(string+" ->"+map.get(string));
工作原理
第一个大括号创建一个新的匿名内部类。这些内部类能够访问其父类的行为。所以,在我们的例子中,我们实际上是在创建一个 HashSet 类的子类,所以这个内部类能够使用 put() 方法。
而第二组大括号不过是实例初始化器。如果您还记得核心 Java 概念,那么您可以轻松地将实例初始化程序块与静态初始化程序关联起来,因为它与 struct 类似。唯一的区别是静态初始化器添加了静态关键字,并且只运行一次;无论您创建多少对象。
more
【讨论】:
【参考方案4】:有关双括号初始化的有趣应用,请参阅此处Dwemthy’s Array in Java。
摘录
private static class IndustrialRaverMonkey
extends Creature.Base
life = 46;
strength = 35;
charisma = 91;
weapon = 2;
private static class DwarvenAngel
extends Creature.Base
life = 540;
strength = 6;
charisma = 144;
weapon = 50;
现在,准备好迎接BattleOfGrottoOfSausageSmells
和……大块培根!
【讨论】:
【参考方案5】:我认为有必要强调Java 中没有“双括号初始化”之类的东西。 Oracle 网站没有这个词。在这个例子中,有两个特性一起使用:匿名类和初始化块。似乎旧的初始化程序块已被开发人员遗忘,并在该主题中引起了一些混乱。来自Oracle docs的引用:
实例变量的初始化块看起来就像静态初始化块,但没有 static 关键字:
// whatever code is needed for initialization goes here
【讨论】:
【参考方案6】:1- 没有双括号这样的东西: 我想指出,没有双括号初始化之类的东西。只有普通的传统大括号初始化块。第二个大括号块与初始化无关。答案说这两个大括号初始化了一些东西,但事实并非如此。
2- 不仅仅是匿名类,而是所有类: 几乎所有的答案都说它是创建匿名内部类时使用的东西。我认为阅读这些答案的人会得到这样的印象,即这只在创建匿名内部类时使用。但它用于所有类。阅读这些答案似乎是一些专门针对匿名课程的全新特殊功能,我认为这是一种误导。
3- 目的只是将括号放在一起,而不是新概念: 更进一步,这个问题讨论了第二个开口括号就在第一个开口括号之后的情况。在普通类中使用时,两个大括号之间通常会有一些代码,但它是完全一样的。所以这是一个放置括号的问题。所以我认为我们不应该说这是一些新的令人兴奋的事情,因为这是我们都知道的事情,只是在括号中写了一些代码。我们不应该创造一个叫做“双括号初始化”的新概念。
4- 创建嵌套匿名类与两个大括号无关: 我不同意您创建了太多匿名类的论点。您创建它们不是因为初始化块,而只是因为您创建了它们。即使您没有使用两个大括号初始化它们也会被创建,因此即使没有初始化也会出现这些问题......初始化不是创建初始化对象的因素。
另外,我们不应该谈论使用这种不存在的东西“双括号初始化”甚至是正常的单括号初始化所产生的问题,因为所描述的问题只是因为创建了匿名类而存在,所以它与原始问题无关.但是所有的答案都给读者的印象是创建匿名类不是错,而是这个邪恶的(不存在的)东西叫做“双括号初始化”。
【讨论】:
【参考方案7】:为了避免双括号初始化的所有负面影响,例如:
-
“等于”兼容性损坏。
使用直接分配时不执行检查。
可能存在内存泄漏。
做接下来的事情:
-
制作单独的“Builder”类,特别是用于双括号初始化。
使用默认值声明字段。
将对象创建方法放在该类中。
例子:
public class MyClass
public static class Builder
public int first = -1 ;
public double second = Double.NaN;
public String third = null ;
public MyClass create()
return new MyClass(first, second, third);
protected final int first ;
protected final double second;
protected final String third ;
protected MyClass(
int first ,
double second,
String third
)
this.first = first ;
this.second= second;
this.third = third ;
public int first () return first ;
public double second() return second;
public String third () return third ;
用法:
MyClass my = new MyClass.Builder() first = 1; third = "3"; .create();
优点:
-
简单易用。
不要破坏“等于”兼容性。
您可以在创建方法中执行检查。
没有内存泄漏。
缺点:
无。因此,我们拥有了最简单的 java builder 模式。
在 github 上查看所有示例:java-sf-builder-simple-example
【讨论】:
MyClass my = new MyClass.Builder().first(1).third("3").create();
至少与您的变体一样简单,无需创建匿名子类。并允许立即验证这些值。【参考方案8】:
正如@Lukas Eder 指出的那样必须避免对集合进行双括号初始化。
它创建了一个匿名内部类,并且由于所有内部类都保留对父实例的引用,因此如果这些集合对象被更多对象引用而不仅仅是声明的对象,它可以 - 并且 99% 的可能性会阻止垃圾回收。
Java 9 引入了便捷方法List.of
、Set.of
和Map.of
,应该使用它们来代替。它们比双大括号初始化器更快、更高效。
【讨论】:
【参考方案9】:它是 - 除其他用途外 - 用于初始化集合的快捷方式。 Learn more ...
【讨论】:
嗯,这是它的一个应用程序,但绝不是唯一一个。【参考方案10】:你的意思是这样的?
List<String> blah = new ArrayList<String>()add("asdfa");add("bbb");;
它是创建时的数组列表初始化(hack)
【讨论】:
【参考方案11】:您可以将一些Java语句作为循环来初始化集合:
List<Character> characters = new ArrayList<Character>()
for (char c = 'A'; c <= 'E'; c++) add(c);
;
Random rnd = new Random();
List<Integer> integers = new ArrayList<Integer>()
while (size() < 10) add(rnd.nextInt(1_000_000));
;
但是这种情况会影响性能,请检查此discussion
【讨论】:
【参考方案12】:第一个大括号创建一个新的匿名类,第二个大括号创建一个实例初始化器,如静态块。
就像其他人指出的那样,使用它并不安全。
但是,您始终可以使用此替代方法来初始化集合。
Java 8List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
Java 9
List<String> list = List.of("A", "B", "C");
【讨论】:
【参考方案13】:这似乎与在 flash 和 vbscript 中流行的 with 关键字相同。这是一种改变this
的方法,仅此而已。
【讨论】:
并非如此。这就像说创建一个新类是一种改变this
的方法。该语法只是创建了一个匿名类(因此任何对this
的引用都将引用该新匿名类的对象),然后使用初始化块...
来初始化新创建的实例。以上是关于什么是 Java 中的双大括号初始化?的主要内容,如果未能解决你的问题,请参考以下文章
我们可以在 C++14 中省略 std::array 的双括号吗?