Efficiency of Java "Double Brace Initialization"? Efficiency of Java "Double Brace Initialization"? java java

Efficiency of Java "Double Brace Initialization"?


Here's the problem when I get too carried away with anonymous inner classes:

2009/05/27  16:35             1,602 DemoApp2$1.class2009/05/27  16:35             1,976 DemoApp2$10.class2009/05/27  16:35             1,919 DemoApp2$11.class2009/05/27  16:35             2,404 DemoApp2$12.class2009/05/27  16:35             1,197 DemoApp2$13.class/* snip */2009/05/27  16:35             1,953 DemoApp2$30.class2009/05/27  16:35             1,910 DemoApp2$31.class2009/05/27  16:35             2,007 DemoApp2$32.class2009/05/27  16:35               926 DemoApp2$33$1$1.class2009/05/27  16:35             4,104 DemoApp2$33$1.class2009/05/27  16:35             2,849 DemoApp2$33.class2009/05/27  16:35               926 DemoApp2$34$1$1.class2009/05/27  16:35             4,234 DemoApp2$34$1.class2009/05/27  16:35             2,849 DemoApp2$34.class/* snip */2009/05/27  16:35               614 DemoApp2$40.class2009/05/27  16:35             2,344 DemoApp2$5.class2009/05/27  16:35             1,551 DemoApp2$6.class2009/05/27  16:35             1,604 DemoApp2$7.class2009/05/27  16:35             1,809 DemoApp2$8.class2009/05/27  16:35             2,022 DemoApp2$9.class

These are all classes which were generated when I was making a simple application, and used copious amounts of anonymous inner classes -- each class will be compiled into a separate class file.

The "double brace initialization", as already mentioned, is an anonymous inner class with an instance initialization block, which means that a new class is created for each "initialization", all for the purpose of usually making a single object.

Considering that the Java Virtual Machine will need to read all those classes when using them, that can lead to some time in the bytecode verfication process and such. Not to mention the increase in the needed disk space in order to store all those class files.

It seems as if there is a bit of overhead when utilizing double-brace initialization, so it's probably not such a good idea to go too overboard with it. But as Eddie has noted in the comments, it's not possible to be absolutely sure of the impact.


Just for reference, double brace initialization is the following:

List<String> list = new ArrayList<String>() {{    add("Hello");    add("World!");}};

It looks like a "hidden" feature of Java, but it is just a rewrite of:

List<String> list = new ArrayList<String>() {    // Instance initialization block    {        add("Hello");        add("World!");    }};

So it's basically a instance initialization block that is part of an anonymous inner class.


Joshua Bloch's Collection Literals proposal for Project Coin was along the lines of:

List<Integer> intList = [1, 2, 3, 4];Set<String> strSet = {"Apple", "Banana", "Cactus"};Map<String, Integer> truthMap = { "answer" : 42 };

Sadly, it didn't make its way into neither Java 7 nor 8 and was shelved indefinitely.


Experiment

Here's the simple experiment I've tested -- make 1000 ArrayLists with the elements "Hello" and "World!" added to them via the add method, using the two methods:

Method 1: Double Brace Initialization

List<String> l = new ArrayList<String>() {{  add("Hello");  add("World!");}};

Method 2: Instantiate an ArrayList and add

List<String> l = new ArrayList<String>();l.add("Hello");l.add("World!");

I created a simple program to write out a Java source file to perform 1000 initializations using the two methods:

Test 1:

class Test1 {  public static void main(String[] s) {    long st = System.currentTimeMillis();    List<String> l0 = new ArrayList<String>() {{      add("Hello");      add("World!");    }};    List<String> l1 = new ArrayList<String>() {{      add("Hello");      add("World!");    }};    /* snip */    List<String> l999 = new ArrayList<String>() {{      add("Hello");      add("World!");    }};    System.out.println(System.currentTimeMillis() - st);  }}

Test 2:

class Test2 {  public static void main(String[] s) {    long st = System.currentTimeMillis();    List<String> l0 = new ArrayList<String>();    l0.add("Hello");    l0.add("World!");    List<String> l1 = new ArrayList<String>();    l1.add("Hello");    l1.add("World!");    /* snip */    List<String> l999 = new ArrayList<String>();    l999.add("Hello");    l999.add("World!");    System.out.println(System.currentTimeMillis() - st);  }}

Please note, that the elapsed time to initialize the 1000 ArrayLists and the 1000 anonymous inner classes extending ArrayList is checked using the System.currentTimeMillis, so the timer does not have a very high resolution. On my Windows system, the resolution is around 15-16 milliseconds.

The results for 10 runs of the two tests were the following:

Test1 Times (ms)           Test2 Times (ms)----------------           ----------------           187                          0           203                          0           203                          0           188                          0           188                          0           187                          0           203                          0           188                          0           188                          0           203                          0

As can be seen, the double brace initialization has a noticeable execution time of around 190 ms.

Meanwhile, the ArrayList initialization execution time came out to be 0 ms. Of course, the timer resolution should be taken into account, but it is likely to be under 15 ms.

So, there seems to be a noticeable difference in the execution time of the two methods. It does appear that there is indeed some overhead in the two initialization methods.

And yes, there were 1000 .class files generated by compiling the Test1 double brace initialization test program.


One property of this approach that has not been pointed out so far is that because you create inner classes, the whole containing class is captured in its scope. This means that as long as your Set is alive, it will retain a pointer to the containing instance (this$0) and keep that from being garbage-collected, which could be an issue.

This, and the fact that a new class gets created in the first place even though a regular HashSet would work just fine (or even better), makes me not want to use this construct (even though I really long for the syntactic sugar).

Second question: The new HashSet must be the "this" used in the instance initializer ... can anyone shed light on the mechanism? I'd have naively expected "this" to refer to the object initializing "flavors".

This is just how inner classes work. They get their own this, but they also have pointers to the parent instance, so that you can call methods on the containing object as well. In case of a naming conflict, the inner class (in your case HashSet) takes precedence, but you can prefix "this" with a classname to get the outer method as well.

public class Test {    public void add(Object o) {    }    public Set<String> makeSet() {        return new HashSet<String>() {            {              add("hello"); // HashSet              Test.this.add("hello"); // outer instance             }        };    }}

To be clear on the anonymous subclass being created, you could define methods in there as well. For example override HashSet.add()

    public Set<String> makeSet() {        return new HashSet<String>() {            {              add("hello"); // not HashSet anymore ...            }            @Override            boolean add(String s){            }        };    }


Every time someone uses double brace initialisation, a kitten gets killed.

Apart from the syntax being rather unusual and not really idiomatic (taste is debatable, of course), you are unnecessarily creating two significant problems in your application, which I've just recently blogged about in more detail here.

1. You're creating way too many anonymous classes

Each time you use double brace initialisation a new class is made. E.g. this example:

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

... will produce these classes:

Test$1$1$1.classTest$1$1$2.classTest$1$1.classTest$1.classTest.class

That's quite a bit of overhead for your classloader - for nothing! Of course it won't take much initialisation time if you do it once. But if you do this 20'000 times throughout your enterprise application... all that heap memory just for a bit of "syntax sugar"?

2. You're potentially creating a memory leak!

If you take the above code and return that map from a method, callers of that method might be unsuspectingly holding on to very heavy resources that cannot be garbage collected. Consider the following example:

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

The returned Map will now contain a reference to the enclosing instance of ReallyHeavyObject. You probably don't want to risk that:

Memory Leak Right Here

Image from http://blog.jooq.org/2014/12/08/dont-be-clever-the-double-curly-braces-anti-pattern/

3. You can pretend that Java has map literals

To answer your actual question, people have been using this syntax to pretend that Java has something like map literals, similar to the existing array literals:

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

Some people may find this syntactically stimulating.