什么是Java中的Double Brace初始化?

什么是Java中的Double Brace初始化语法( {{ ... }}

双括号初始化创build一个从指定类( 括号)派生的匿名类,并在该类内提供一个初始化块( 括号)。 例如

 new ArrayList<Integer>() {{ add(1); add(2); }}; 

请注意,使用此大括号初始化的效果是您正在创build匿名内部类。 创build的类有一个隐含的指向周围外部类的指针。 虽然通常不是一个问题,但在某些情况下,例如连载或垃圾收集时,可能会引起悲伤,值得注意。

每次有人使用双括号初始化,一只小猫被杀死。

除了语法相当不寻常,并不是真正的习惯(当然,味道是有争议的),你不必要地在你的应用程序中创build了两个重要的问题, 我刚刚在这里更详细地提到了这个问题 。

你正在创造太多的匿名类

每次使用双括号初始化时,都会创build一个新类。 比如这个例子:

 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的封闭实例的ReallyHeavyObject 。 你可能不想冒这个风险:

内存泄漏在这里

来自http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/的图片;

你可以假装Java有地图文字

为了回答你的实际问题,人们一直在使用这种语法来假装Java有像地图文字一样的东西,类似于现有的数组文字:

 String[] array = { "John", "Doe" }; Map map = new HashMap() {{ put("John", "Doe"); }}; 

有些人可能会发现这种句法刺激。

TL;博士
1.第一个大括号创build一个新的匿名内部类。
2.第二组大括号在Class中创build一个像静态块一样的实例初始化器。


例如:

  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)); } } } 

怎么运行的

第一个大括号创build一个新的匿名内部类。 这些内部类能够访问其父类的行为。 所以,在我们的例子中,我们实际上是在创build一个HashSet类的子类,所以这个内部类可以使用add()方法。

第二组大括号只是实例初始值设定项。 如果您提醒核心Java概念,那么您可以轻松地将实例初始化程序块与静态初始化程序相关联,因为类似的大括号结构。 唯一不同的是,静态初始化器添加了static关键字,并且只运行一次; 不pipe你创build了多less个对象

更多

有关双重大括号初始化的有趣应用程序,请参阅此处的Java中的Dwemthy数组 。

摘录

 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和… BattleOfGrottoOfSausageSmells 培根!

我想指出的是,没有这样的事情双重大括号初始化。 只有正常的传统的一个支撑初始化块。 第二个括号块与初始化无关。 回答说,这两个大括号初始化的东西,但它不是这样的。

其次,几乎所有的答案都说这是创build匿名内部类时使用的东西。 我认为阅读这些答案的人会得到这样的印象,即只有在创build匿名内部类时才会这样做。 但它被用在所有的类中。 阅读这些答案看起来像是一个全新的专门为匿名课程而devise的未来,我认为这是一个误导。

再进一步,这个问题是关于第二个开放支架在第一个开放支架之后的情况。 当在普通类中使用时,通常在两个大括号之间有一些代码,但它是完全相同的东西。 所以这是放置括号的问题。 所以我认为我们不应该说这是一个新的激动人心的事情,因为这是我们都知道的事情,但是只是用一些括号内的代码写的。 我们不应该创造一个新的概念,叫“双支撑初始化”。

我不同意你创build了太多的匿名类。 你不会创build它们,因为它是一个初始化块,但仅仅是因为你创build了它们。 即使你没有使用两个大括号初始化它们也会被创build,所以即使没有初始化也会发生这些问题…初始化不是创build初始化对象的因素。

此外,我们不应该谈论使用这个不存在的事物“双重大括号初始化”或甚至通过正常的一个括号初始化创build的问题,因为所描述的问题仅仅是因为创build匿名类而存在,因此与原始问题无关。 但是,所有的答案都给读者留下了印象,那就是创build匿名类并不是错,而是这个邪恶(不存在)的东西叫做“双重大括号初始化”。

我认为重要的是要强调在Java中没有“Double Brace initialization”这样的东西 。 Oracle网站没有这个词。 在这个例子中,有两个特征一起使用:匿名类和初始化块。 似乎开发人员已经忘记了旧的初始化块,并在这个主题中引起了一些混淆。 从Oracle文档引用:

实例variables的初始化块与静态初始化块相似,但没有static关键字:

 { // whatever code is needed for initialization goes here } 

这是一个初始化集合的捷径。 学到更多 …

你的意思是这样的吗?

 List<String> blah = new ArrayList<String>(){{add("asdfa");add("bbb");}}; 

这是一个数组列表初始化创build时间(黑客)

您可以将一些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)); } }; 

但这种情况影响到性能,请检查这个讨论

为了避免双括号初始化的所有负面影响,例如:

  1. 断开“等于”兼容性。
  2. 使用直接分配时,不执行检查。
  3. 可能的内存泄漏。

做下一件事情:

  1. 分开的“生成器”类特别是双重大括号初始化。
  2. 用默认值声明字段。
  3. 把对象创build方法放在那个类中。

例:

 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(); 

优点:

  1. 只需使用。
  2. 不要打破“等于”的兼容性。
  3. 您可以在创build方法中执行检查。
  4. 没有内存泄漏。

缺点:

  • 没有。

结果,我们有了最简单的java builder模式。

请参阅github上的所有示例: java-sf-builder-simple-example

这似乎与在flash和vbscript中非常stream行的with关键字一样。 这是一个改变this是什么的方法,仅this而已。