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


305

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




10
Double Brace初始化是一个非常危险的功能,应谨慎使用。它可能会破坏等价合约并引入棘手的内存泄漏。文章描述的细节。
Andrii Polunin 2012年

Answers:


301

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

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

请注意,使用双括号初始化的作用是创建匿名内部类。创建的类具有this指向周围外部类的隐式指针。尽管通常不是问题,但在某些情况下(例如序列化或垃圾收集时)可能会引起悲伤,值得一提的是。


11
感谢您澄清内括号和外括号的含义。我想知道为什么突然有两个括号带有特殊含义,而实际上它们却是普通的Java结构,它们只是一些神奇的新技巧而已。诸如此类的事情让我质疑Java语法。如果您还不是专家,那么阅读和书写可能会非常棘手。
jackthehipster 2014年

4
这样的“魔术语法”在许多语言中都存在,例如,几乎所有类似于C的语言都在for循环中支持“ x-> 0”的“ goes to 0”语法,而这只是“ x--> 0”而又很奇怪空间位置。
约阿希姆·绍尔

17
我们可以得出结论,“双括号初始化”本身并不存在,它只是创建一个匿名类和一个初始值设定项块的组合,后者组合起来看起来像是一种语法构造,但实际上,它不是“ t。
MC Emperor

谢谢!由于匿名内部类的使用,当我们使用双花括号初始化序列化某些内容时,Gson返回null。
Pradeep AJ- Msft MVP,

294

每当有人使用双括号初始化时,就会杀死一只小猫。

除了语法非常不寻常且不是真正惯用的(当然,味道是值得商bat的)之外,您还在应用程序中不必要地创建了两个重大问题,我最近在此处对此进行了详细介绍

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

有些人可能会从语法上发现这种刺激。


11
“您创建的匿名类太多了”-查看Scala如何创建匿名类,我不太确定这是一个主要问题
Brian Agnew

2
它仍然不是声明​​静态映射的有效方法吗?如果使用HashMap初始化{{...}}并声明为static字段,则不应有任何可能的内存泄漏,只有一个匿名类并且没有封闭的实例引用,对吗?
lorenzo-s

8
@ lorenzo-s:是的,2)和3)则不适用,只有1)。幸运的是,有了Java 9,终于有了Map.of()这个目的,因此这将是一个更好的解决方案
Lukas Eder

3
可能值得注意的是,内部映射也引用了外部映射,因此也间接引用了ReallyHeavyObject。另外,匿名内部类会捕获在类主体中使用的所有局部变量,因此,如果您不仅使用常量以这种模式初始化集合或映射,内部类实例将捕获所有它们并继续引用它们,即使实际上已从中删除它们也是如此。集合或地图。因此,在这种情况下,这些实例不仅需要为引用添加两倍的必要内存,而且在这方面还存在另一个内存泄漏。
Holger

41
  • 第一个括号创建一个新的匿名内部类。
  • 第二组括号创建一个实例初始化程序,例如Class中的static块。

例如:

   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关键字,并且只能运行一次;无论您创建多少个对象。

更多


24

有关双括号初始化的有趣应用程序,请参见此处的Dwemthy的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…和… 厚实的培根做好准备


16

我认为必须强调的是,Java中没有“ Double Brace初始化”之类的东西。Oracle网站没有这个术语。在此示例中,一起使用了两个功能:匿名类和初始化程序块。似乎旧的初始化程序块已被开发人员遗忘,并在此主题中造成了一些混乱。Oracle文档的引文:

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

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

11

1-没有双括号的东西:
我想指出的是,没有双括号初始化的东西。只有正常的传统一括号初始化块。第二大括号块与初始化无关。答案说那两个大括号初始化了一些东西,但事实并非如此。

2-不仅仅与匿名类有关,还与所有类有关:
几乎所有答案都说这是创建匿名内部类时要使用的东西。我认为阅读这些答案的人会给人以为这仅在创建匿名内部类时使用。但是,所有类都使用它。阅读这些答案似乎是专门针对匿名课程的一些全新特殊功能,我认为这具有误导性。

3-目的只是关于将支架相互放置,而不是新的概念:
进一步讲,这个问题是关于第二个支架在第一个支架之后的情况。在普通班级中使用时,通常在两个花括号之间有一些代码,但这是完全相同的。因此,这是放置括号的问题。因此,我认为我们不应该说这是一个令人兴奋的新事物,因为这是众所周知的事情,而只是在方括号之间编写了一些代码。我们不应创建称为“双括号初始化”的新概念。

4-创建嵌套的匿名类与两个花括号无关:
我不同意创建过多匿名类的说法。您之所以创建它们不是因为初始化块,而是因为您创建了它们。即使您不使用两个花括号初始化,也将创建它们,因此,即使不进行初始化也会出现这些问题。初始化不是创建初始化对象的因素。

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


9

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

  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



4

你的意思是这样的吗?

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

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


4

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

但是这种情况会影响性能,请检查此讨论


4

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

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

Java 9引入了便捷方法List.ofSet.ofMap.of,应该使用它们。它们比双括号初始化器更快,更高效。


0

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

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

但是,您始终可以使用此替代方案来初始化集合。

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

-1

这似乎与在Flash和vbscript中非常流行的with关键字相同。这是一种改变事物的方法,this仅此而已。


并不是的。这就像说创建一个新类是一种更改内容的方法this。该语法仅创建一个匿名类(因此对它的任何引用this都将引用该新匿名类的对象),然后使用初始化程序块{...}来初始化新创建的实例。
格林奇
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.