什么是Java中的双大括号初始化?

Java中的双大括号初始化语法({{ ... }})是什么?

116166 次浏览

在其他用途中,它是初始化集合的快捷方式。了解更多…

双大括号初始化创建一个从指定类派生的匿名类(大括号),并在该类中提供一个初始化程序块(内心的大括号)。如。

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

注意,使用这种双大括号初始化的效果是创建匿名的内部类。创建的类有一个隐式的this指针指向周围的外部类。虽然通常不是问题,但在某些情况下,例如序列化或垃圾收集时,它可能会导致痛苦,值得注意这一点。

这似乎与flash和vbscript中流行的with关键字相同。它是一种改变this的方法,仅此而已。

有关双大括号初始化的有趣应用,请参阅这里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和…厚实的熏肉!

你是说像这样的事?

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

这是在创建时初始化数组列表(hack)

你可以把一些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. 你创建了太多的匿名类

每次使用双大括号初始化都会创建一个新类。例如这个例子:

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

这对您的类加载器来说是相当大的开销——毫无意义!当然,如果你只做一次,就不会花费太多初始化时间。但是如果你在整个企业应用程序中做了20000次这样的事情……那么多内存只是为了一点“语法糖”?

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

Memory Leak Right Here

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

3.你可以假装Java有地图字面量

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

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

有些人可能会觉得这在语法上很刺激。

  • 第一个大括号创建了一个新的匿名内部类。
  • 第二组大括号创建了一个实例初始化器,类似于Class中的static block。

例如:

   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的大括号。唯一的区别是静态初始化器添加了static关键字,并且只运行一次;不管你创建了多少个对象。

more

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

  1. 打破了“平等”的兼容性。
  2. 使用直接赋值时不执行检查。
  3. 可能存在内存泄漏。

做下面的事情:

  1. 创建单独的“Builder”类,专门用于双大括号初始化。
  2. 用默认值声明字段。
  3. 将对象创建方法放在该类中。

例子:

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. 您可以在创建方法中执行检查。
  4. 无内存泄漏。

缺点:

  • 一个也没有。

因此,我们有了有史以来最简单的java构建器模式。

查看所有示例:github: java-sf-builder-simple-example

< p > 1-没有双括号这样的东西: < br > 我想指出,没有双大括号初始化这样的东西。传统的初始化块只有一个大括号。第二个花括号块与初始化无关。答案说这两个大括号初始化了一些东西,但不是这样的 < p > 2-不只是匿名类,而是所有类: < br > 几乎所有的答案都说它是在创建匿名内部类时使用的。我认为阅读这些答案的人会得到这样的印象,即这只在创建匿名内部类时使用。但它在所有课程中都被使用。读这些答案,它看起来像是一些全新的专门用于匿名类的特殊功能,我认为这是误导 < p > 3-目的只是把括号放在后面,不是新概念: < br > 进一步说,这个问题讨论的是第二个括号在第一个括号之后的情况。当在普通类中使用时,通常在两个大括号之间有一些代码,但这是完全相同的事情。所以这是一个放置括号的问题。所以我认为我们不应该说这是什么令人兴奋的新东西,因为这是我们都知道的东西,只是在括号中间写了一些代码。我们不应该创造“双括号初始化”的新概念。

< p > 创建嵌套的匿名类与两个大括号无关: < br > 我不同意创建太多匿名类的说法。你创建它们不是因为一个初始化块,而是因为你创建了它们。即使你没有使用两个大括号初始化,它们也会被创建,所以即使没有初始化,这些问题也会发生……初始化不是创建初始化对象的因素

此外,我们不应该谈论使用这个不存在的东西“双括号初始化”,甚至是普通的单括号初始化所产生的问题,因为所描述的问题只是因为创建匿名类而存在,所以它与原始问题无关。但是所有的答案都给读者留下了这样的印象:创建匿名类并不是错误,而是这个邪恶的(不存在的)叫做“双括号初始化”的东西。

我认为有必要强调在Java中没有“双大括号初始化”这样的东西。甲骨文网站没有这个术语。在这个例子中,有两个特性一起使用:匿名类和初始化程序块。开发人员似乎已经忘记了旧的初始化程序块,并在这个主题中引起了一些混乱。引用自甲骨文公司文档:

实例变量的初始化块看起来就像静态初始化块,但是没有static关键字:

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

正如@Lukas Eder 必须避免对集合进行双花括号初始化。所指出的

它创建了一个匿名的内部类,并且由于所有内部类都保留了对父实例的引用,所以如果这些集合对象被其他对象引用,而不仅仅是声明的对象引用,那么它可以(99%可能会)防止垃圾收集。

Java 9引入了方便的方法List.ofSet.ofMap.of,应该使用它们来代替。它们比双括号初始化式更快更有效。

第一个大括号创建了一个新的匿名类,第二组大括号创建了一个实例初始化器,类似于静态块。

正如其他人指出的那样,使用它并不安全。

但是,您总是可以使用这个替代方法来初始化集合。

  • Java 8
List<String> list = new ArrayList<>(Arrays.asList("A", "B", "C"));
  • Java 9
List<String> list = List.of("A", "B", "C");