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

What is Double Brace initialization in Java?

Java中双括号初始化语法(EDCOX1×0)是什么?


每次有人用双括号初始化,小猫就会被杀死。

除了语法很不寻常而且不太习惯(当然,味觉是有争议的),您在应用程序中不必要地创建了两个重要问题,我最近在博客中更详细地介绍了这两个问题。

1。你创建的匿名类太多了

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

1
2
3
4
5
6
7
8
9
10
11
12
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");
        }});
    }});
}};

…将生成这些类:

1
2
3
4
5
Test$1$1$1.class
Test$1$1$2.class
Test$1$1.class
Test$1.class
Test.class

对于您的类加载器来说,这是一个相当大的开销——毫无意义!当然,如果你只做一次,初始化时间不会太长。但是,如果您在整个企业应用程序中这样做了20000次…所有这些堆内存只是为了一点"语法糖"?

2。您可能会造成内存泄漏!

如果您使用上述代码并从一个方法返回该映射,那么该方法的调用方可能会毫无疑问地持有无法进行垃圾收集的大量资源。请考虑以下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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

image from http://blog.jooq.org/2014/12/08/dont be smart the double curly brakes anti pattern/

三。你可以假装Java有地图文字。

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

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

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


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

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

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


  • 第一个大括号创建一个新的匿名内部类。
  • 第二组大括号创建实例初始值设定项,如类中的静态块。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
   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概念,那么由于类似的支撑结构,您可以很容易地将实例初始化器块与静态初始化器关联起来。唯一的区别是静态初始值设定项是用静态关键字添加的,并且只运行一次;无论您创建了多少对象。

更多


有关双括号初始化的有趣应用程序,请参阅Java中的DeMeMy数组。

节选

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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和&hellip;大块培根吧!


我认为强调在爪哇没有"双括号初始化"是很重要的。Oracle网站没有此术语。在这个例子中,有两个共同使用的特性:匿名类和初始值设定项块。似乎开发人员已经忘记了旧的初始值设定项块,并在这个主题中引起了一些混乱。Oracle文档引文:

实例变量的初始值设定项块看起来就像静态初始值设定项块,但没有静态关键字:

1
2
3
{
    // whatever code is needed for initialization goes here
}

1-没有双大括号:我想指出的是,没有双括号初始化这样的事情。只有普通的传统单括号初始化块。第二个大括号块与初始化无关。答案是这两个大括号初始化了某个东西,但不是这样的。

2-这不仅仅是匿名类,而是所有类:几乎所有的答案都说这是创建匿名内部类时使用的一种方法。我认为,阅读这些答案的人会有这样的印象,即只有在创建匿名的内部类时才使用这些答案。但是它在所有的类中都被使用。阅读这些答案看起来像是一个全新的匿名课程的特色,我认为这是误导。

3-目的是将括号放在一起,而不是新概念:更进一步,这个问题讨论了当第二个开口托架刚好在第一个开口托架之后的情况。当在普通类中使用时,通常在两个大括号之间有一些代码,但这是完全相同的。所以这是一个放置支架的问题。所以我认为我们不应该说这是一个新的令人兴奋的事情,因为这是我们都知道的事情,只是在括号之间写了一些代码。我们不应该创建名为"双括号初始化"的新概念。

4-创建嵌套匿名类与两个大括号无关:我不同意你创建太多匿名类的观点。您创建它们不是因为初始化块,而是因为您创建了它们。即使您没有使用两个大括号初始化,也会创建它们,这样即使没有初始化,这些问题也会发生…初始化不是创建已初始化对象的因素。

Additionally we should not talk about problem created by using this non-existent thing"double brace initialization" or even by normal one bracket initialization, because described problems exist only because of creating anonymous class so it has nothing to do with original question. But all answers with give the readers impression that it is not fault of creating anonymous classes, but this evil (non-existent) thing called"double brace initialization".


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

  • 打破了"等于"的兼容性。
  • 使用直接分配时,不执行检查。
  • 可能内存泄漏。
  • 做下一件事:

  • 创建单独的"builder"类,特别是用于双括号初始化。
  • 用默认值声明字段。
  • 将对象创建方法放入该类中。
  • 例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    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 ; }
    }

    用途:

    1
    MyClass my = new MyClass.Builder(){{ first = 1; third ="3"; }}.create();

    优势:

  • 简单使用。
  • 不要破坏"等于"的兼容性。
  • 您可以在创建方法中执行检查。
  • 没有内存泄漏。
  • 缺点:

    • 一个也没有。

    因此,我们有最简单的Java Builder模式。

    在GITHUB中查看所有示例:Java SF Builder简单示例


    可以将一些Java语句作为循环初始化集合:

    1
    2
    3
    4
    5
    List<Character> characters = new ArrayList<Character>() {
        {
            for (char c = 'A'; c <= 'E'; c++) add(c);
        }
    };
    1
    2
    3
    4
    5
    6
    7
    Random rnd = new Random();

    List<Integer> integers = new ArrayList<Integer>() {
        {
             while (size() < 10) add(rnd.nextInt(1_000_000));
        }
    };

    但是这个案例会影响到性能,请检查这个讨论


    你的意思是这样的?

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

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


    它是一种初始化集合的快捷方式。了解更多…


    正如@lukas eder所指出的,必须避免初始化集合。

    它创建了一个匿名内部类,由于所有内部类都保留对父实例的引用,因此,如果这些集合对象被多个对象引用,而不仅仅是声明对象引用,则99%可能会阻止垃圾收集。

    Java 9引入了方便的方法EDOCX1、0、EDCOX1、1、EDCOX1、2、2。它们比双括号初始值设定项更快更有效。


    这似乎与Flash和vbscript中流行的WITH关键字相同。这是一种改变this的方法,而不是其他方法。