Efficiency of Java "Double Brace Initialization"?
Asked Answered
I

15

911

In Hidden Features of Java the top answer mentions Double Brace Initialization, with a very enticing syntax:

Set<String> flavors = new HashSet<String>() {{
    add("vanilla");
    add("strawberry");
    add("chocolate");
    add("butter pecan");
}};

This idiom creates an anonymous inner class with just an instance initializer in it, which "can use any [...] methods in the containing scope".

Main question: Is this as inefficient as it sounds? Should its use be limited to one-off initializations? (And of course showing off!)

Second question: The new HashSet must be the "this" used in the instance initializer ... can anyone shed light on the mechanism?

Third question: Is this idiom too obscure to use in production code?

Summary: Very, very nice answers, thanks everyone. On question (3), people felt the syntax should be clear (though I'd recommend an occasional comment, especially if your code will pass on to developers who may not be familiar with it).

On question (1), the generated code should run quickly. The extra .class files do cause jar file clutter, and slow program startup slightly (thanks to @coobird for measuring that). @Thilo pointed out that garbage collection can be affected, and the memory cost for the extra loaded classes may be a factor in some cases.

Question (2) turned out to be most interesting to me. If I understand the answers, what's happening in DBI is that the anonymous inner class extends the class of the object being constructed by the new operator, and hence has a "this" value referencing the instance being constructed. Very neat.

Overall, DBI strikes me as something of an intellectual curiousity. Coobird and others point out you can achieve the same effect with Arrays.asList, varargs methods, Google Collections, and the proposed Java 7 Collection literals. Newer JVM languages like Scala, JRuby, and Groovy also offer concise notations for list construction, and interoperate well with Java. Given that DBI clutters up the classpath, slows down class loading a bit, and makes the code a tad more obscure, I'd probably shy away from it. However, I plan to spring this on a friend who's just gotten his SCJP and loves good natured jousts about Java semantics! ;-) Thanks everyone!

7/2017: Baeldung has a good summary of double brace initialization and considers it an anti-pattern.

12/2017: @Basil Bourque notes that in the new Java 9 you can say:

Set<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");

That's for sure the way to go. If you're stuck with an earlier version, take a look at Google Collections' ImmutableSet.

Impropriety answered 29/5, 2009 at 3:40 Comment(13)
The code smell I see here is, that the naive reader would expect flavors to be a HashSet, but alas it is an anonymous subclass.Dimercaprol
If you consider running instead of loading performance there is no difference, see my answer.Compendious
I love that you created a summary, I think this is a worthwhile exercise for both you to increase comprehension and the community.Euplastic
It's not obscure in my opinion. Readers should know that a double ... o wait, @ElazarLeibovich already said that in his comment. Double brace initializer itself doesn't exist as a language construct, it is just a combination of an anonymous subclass and an instance initializer. The only thing is, that people need to be aware of this.Prologue
Efficiency isn't the issue - DBI is dangerous and error-prone.Supercool
Nice thing I saw is the summary; I didnt see this in any other question.Felonry
In my opinion, double brace initialization is a bad idea.Divert
Java 9 offers Immutable Set Static Factory Methods that may replace the use of DCI in some situations: Set<String> flavors = Set.of( "vanilla" , "strawberry" , "chocolate" , "butter pecan" ) ;Spitfire
@Basil Bourque: Yes, thank you, I'm really happy they've added that! I've been using Google Collection's ImmutableSet.of(...) in Java 8 and can't wait until we upgrade to Java 9.Impropriety
There's still no way to easily initialize a mutable collection and no Java 9 List.of is not the same.Falk
In Java 8, you can do Stream.of("vanilla", "strawberry").collect(Collectors::toSet)Parochial
@ElazarLeibovich Usually a particular type is not important because of the subtyping.Vacuous
@Vacuous the key point to understand the code smell here is usually. No clear benefit, and hideous subtle behavioral change.Dimercaprol
F
656

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

2009/05/27  16:35             1,602 DemoApp2$1.class
2009/05/27  16:35             1,976 DemoApp2$10.class
2009/05/27  16:35             1,919 DemoApp2$11.class
2009/05/27  16:35             2,404 DemoApp2$12.class
2009/05/27  16:35             1,197 DemoApp2$13.class

/* snip */

2009/05/27  16:35             1,953 DemoApp2$30.class
2009/05/27  16:35             1,910 DemoApp2$31.class
2009/05/27  16:35             2,007 DemoApp2$32.class
2009/05/27  16:35               926 DemoApp2$33$1$1.class
2009/05/27  16:35             4,104 DemoApp2$33$1.class
2009/05/27  16:35             2,849 DemoApp2$33.class
2009/05/27  16:35               926 DemoApp2$34$1$1.class
2009/05/27  16:35             4,234 DemoApp2$34$1.class
2009/05/27  16:35             2,849 DemoApp2$34.class

/* snip */

2009/05/27  16:35               614 DemoApp2$40.class
2009/05/27  16:35             2,344 DemoApp2$5.class
2009/05/27  16:35             1,551 DemoApp2$6.class
2009/05/27  16:35             1,604 DemoApp2$7.class
2009/05/27  16:35             1,809 DemoApp2$8.class
2009/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.

Fireproofing answered 29/5, 2009 at 3:59 Comment(26)
"Probably" being the operative word. Unless measured, no statements about performance are meaningful.Sapiential
You say "there is actually quite a bit of overhead when performing double-brace initialization," but it really doesn't look like this is the case. I won't believe such a statement without profiling, in any case.Diseased
That's true, it's hard to be truly sure if there is a large overhead with double brace initialization without profiling. I'll change the wording.Fireproofing
Isn't this just because you're loading a class each time?Shoulders
@Niel Coffey: Yes, because each double brace initialization is a separate anonymous inner class, it will create a new class for each initialization.Fireproofing
You've done such a great job I hardly want to say this, but the Test1 times could be dominated by class loads. It would be interesting to see someone run a single instance of each test in a for loop say 1,000 times, then run it again in a second for loop of 1,000 or 10,000 times and print out the time difference (System.nanoTime()). The first for loop should get past all the warm up effects (JIT, classload, eg). Both tests model different use-cases though. I'll try to run this tomorrow at work.Impropriety
@Jim Ferrans: I'm fairly certain that the Test1 times are from class loads. But, the consequence of using double brace initialization is having to cope from class loads. I believe most use cases for double brace init. is for one-time initialization, the test is closer in conditions to a typical use case of this type of initialization. I would believe that multiple iterations of each test would make the execution time gap smaller.Fireproofing
@coobird: Yes, you're right, it would be a very strange use-case that put one of these puppies in an inner loop.Impropriety
Even if you put it into a loop, there would still be only one inner class (just many instances thereof).Mastaba
@coobird: I hope you didn't write that test code by hand! @Jim Ferrans: even if this was in an inner loop, you only have to pay for class loading one time. It's a one-time cost per inner class. @coobird's test had 1000 separate anonymous inner classes!!!Diseased
To do this test and properly "warm up" the code being used, I think you have to use a class loader so you can unload the class objects after each test and thus and force reloading the class objects each time. It's non-trivial.Diseased
@Eddie: Don't worry, I wrote a short program to generate the source code for the tests. :)Fireproofing
What this proves is that a) double-brace initialization is slower, and b) even if you do it 1000 times, you probably won't notice the difference. And it's not like this could be the bottleneck in an inner loop, either. It imposes a tiny one-time penalty AT THE VERY WORST.Fossilize
That and a bit of permgen space, actually. But they're such tiny classes that I doubt that's much of a problem.Fossilize
If using DBI makes the code more readable or expressive, then use it. The fact that it increases a bit the work the JVM has to perform is not a valid argument, in itself, against it. If it were, then we should also be worried about extra helper methods/classes, preferring instead huge classes with fewer methods...Medellin
If you consider running instead of loading performance there is no difference, see my answer.Compendious
"Not to mention the increase in the needed disk space" - Forget disk space; think of what all those anonymous class definitions will do to your PermGen space! All of those anonymous class definitions get loaded there, and they don't get unloaded. That makes using double-brace initialization in any context other than a static initializer (or equivalent 'one-off' scope) a memory leak.Kozhikode
@MichaelMyers - One needs only to look at the number of questions related to exhausting the PermGen space to see that you really don't want to be unnecessarily wasting it even on "such tiny classes", doubly so in any server context. Or any other context in which an application is expected to run for long periods of time and generally, you know, not crash due to memory leaks.Kozhikode
Should the compiler optimize such cases, by detecting such an instance initialization (anonymous class has no overridden methods and fields, and only has an initialization block) and then replace it with a creation of the superclass (e.g. List<String> lst = new ArrayList<>()), followed by the contents of the initializer block (e.g. lst.add("Hello"); lst.add("World!")), so the actual anonymous subclass is optimized away?Prologue
BTW, Java 9 is getting collection factory methods, so DBI should be less tempting. openjdk.java.net/jeps/269Calamitous
Actually first {} block means anonymous class inherited from ArrayList<String> class. And 2nd {} is instance initialization block of anonymous class. There is NO any inner class.Moneyed
Your Test2 is not valid test. You could have only 0 in your test, because of java optimization. You should, for example, print size of created list in Test2.Abigael
@MCEmperor: if you tell the compiler you want a subclass, the compiler will give you a subclass. You can be sure that lst.getClass()==ArrayList.class will evaluate to false. No optimizing compiler is allowed to change the semantic of the program, even if it is very likely that the programmer doesn’t actually want that semantic.Outreach
Now with Java 9, you can use List<String> l1 = new ArrayList<>(List.of("Hello", "World")); or even List<String> l1 = List.of("Hello", "World");, if you don’t need an ArrayList, but in the latter case, you could have used List<String> l1 = Arrays.asList("Hello", "World"); since Java 5.Outreach
only one good syntactic sugar from java and even that is not recommended...Cytologist
@M.kazemAkhgary that’s not “good syntactic sugar”. Actually, the OP’s example Set<String> flavors = new HashSet<String>() {{ add("vanilla"); add("strawberry"); add("chocolate"); add("butter pecan"); }}; does not save anything compared to Set<String> flavors = new HashSet<>(); Collections.addAll(flavors, "vanilla", "strawberry", "chocolate", "butter pecan");. Even a sequence of four manual flavor.add calls is not much longer than the “syntactic sugar”. Then, there’s the Java 9 alternative Set<String> flavors = Set.of("vanilla", "strawberry", "chocolate", "butter pecan");.Outreach
M
115

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){

            }

        };
    }
Mastaba answered 29/5, 2009 at 5:37 Comment(3)
Very good point on the hidden reference to the containing class. In the original example, the instance initializer is calling the add() method of the new HashSet<String>, not Test.this.add(). That suggests to me that something else is happening. Is there an anonymous inner class for the HashSet<String>, as Nathan Kitchen suggests?Impropriety
The reference to the containing class might also be dangerous if serialization of the datastructure is involved. The containing class will also be serialized and thus must be Serializable. This can lead to obscure errors.Habergeon
And it’s not only this$0. When this trick is used with non-constant values, the values of the accessed variables get captured too and stay referenced even when you remove the corresponding elements from the collection. In the most extreme case, you could end up with an empty collection having hundreds of references to objects it was initialized with.Outreach
N
73

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.class
Test$1$1$2.class
Test$1$1.class
Test$1.class
Test.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.

Neslund answered 17/12, 2014 at 8:56 Comment(0)
D
41

Taking the following test class:

public class Test {
  public void test() {
    Set<String> flavors = new HashSet<String>() {{
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
    }};
  }
}

and then decompiling the class file, I see:

public class Test {
  public void test() {
    java.util.Set flavors = new HashSet() {

      final Test this$0;

      {
        this$0 = Test.this;
        super();
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
      }
    };
  }
}

This doesn't look terribly inefficient to me. If I were worried about performance for something like this, I'd profile it. And your question #2 is answered by the above code: You're inside an implicit constructor (and instance initializer) for your inner class, so "this" refers to this inner class.

Yes, this syntax is obscure, but a comment can clarify obscure syntax usage. To clarify the syntax, most people are familiar with a static initializer block (JLS 8.7 Static Initializers):

public class Sample1 {
    private static final String someVar;
    static {
        String temp = null;
        ..... // block of code setting temp
        someVar = temp;
    }
}

You can also use a similar syntax (without the word "static") for constructor usage (JLS 8.6 Instance Initializers), although I have never seen this used in production code. This is much less commonly known.

public class Sample2 {
    private final String someVar;

    // This is an instance initializer
    {
        String temp = null;
        ..... // block of code setting temp
        someVar = temp;
    }
}

If you don't have a default constructor, then the block of code between { and } is turned into a constructor by the compiler. With this in mind, unravel the double brace code:

public void test() {
  Set<String> flavors = new HashSet<String>() {
      {
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
      }
  };
}

The block of code between the inner-most braces is turned into a constructor by the compiler. The outer-most braces delimit the anonymous inner class. To take this the final step of making everything non-anonymous:

public void test() {
  Set<String> flavors = new MyHashSet();
}

class MyHashSet extends HashSet<String>() {
    public MyHashSet() {
        add("vanilla");
        add("strawberry");
        add("chocolate");
        add("butter pecan");
    }
}

For initialization purposes, I'd say there is no overhead whatsoever (or so small that it can be neglected). However, every use of flavors will go not against HashSet but instead against MyHashSet. There is probably a small (and quite possibly negligible) overhead to this. But again, before I worried about it, I would profile it.

Again, to your question #2, the above code is the logical and explicit equivalent of double brace initialization, and it makes it obvious where "this" refers: To the inner class that extends HashSet.

If you have questions about the details of instance initializers, check out the details in the JLS documentation.

Diseased answered 29/5, 2009 at 3:52 Comment(6)
Eddie, very nice explanation. If the JVM byte codes are as clean as the decompilation, the execution speed will be fast enough, though I'd be somewhat concerned about the extra .class file clutter. I'm still curious as to why the constructor of the instance initializer sees "this" as the new HashSet<String> instance and not the Test instance. Is this just explicitly specified behavior in the latest Java Language Specification to support the idiom?Impropriety
I updated my answer. I left out the boilerplate of the Test class, which caused the confusion. I put it into my answer to make things more obvious. I also mention the JLS section for the instance initializer blocks used in this idiom.Diseased
@Jim The interpretation of "this" is not a special case; it simply refers to the instance of the innermost enclosing class, which is the anonymous subclass of HashSet<String>.Teleology
Sorry to jump in four and a half years later. But the nice thing about the decompiled class file (your second code block) is that it's not valid Java! It has super() as the second line of the implicit constructor, but it has to come first. (I've tested it, and it won't compile.)Updo
@chiastic-security: Sometimes decompilers generate code that won't compile.Diseased
If we ignore the performance aspect, Set<String> flavors = new HashSet<String>(); Collections.addAll(flavors, "vanilla", "strawberry", "chocolate", "butter pecan"); still is shorter, more readable, also works with collection types not supporting subclassing, and has none of the double curly brace disadvantages.Outreach
S
37

leak prone

I've decided to chime in. The performance impact includes: disk operation + unzip (for jar), class verification, perm-gen space (for Sun's Hotspot JVM). However, worst of all: it's leak prone. You can't simply return.

Set<String> getFlavors(){
  return Collections.unmodifiableSet(flavors)
}

So if the set escapes to any other part loaded by a different classloader and a reference is kept there, the entire tree of classes+classloader will be leaked. To avoid that, a copy to HashMap is necessary, new LinkedHashSet(new ArrayList(){{add("xxx);add("yyy");}}). Not so cute any more. I don't use the idiom, myself, instead it is like new LinkedHashSet(Arrays.asList("xxx","YYY"));

Syrian answered 29/5, 2009 at 3:40 Comment(2)
Luckily, as of Java 8, PermGen is no longer a thing. There's still an impact, I guess, but not one with a potentially very obscure error message.Acrylonitrile
@Joey, makes zero difference if the memory is managed directly by the GC (perm gen) or not. A leak in metaspace is still a leak, unless meta is limited there won't be a OOM (out of perm gen) by stuff like oom_killer in linux is going to kick in.Syrian
C
19

Loading many classes can add some milliseconds to the start. If the startup isn't so critical and you are look at the efficiency of classes after startup there is no difference.

package vanilla.java.perfeg.doublebracket;

import java.util.*;

/**
 * @author plawrey
 */
public class DoubleBracketMain {
    public static void main(String... args) {
        final List<String> list1 = new ArrayList<String>() {
            {
                add("Hello");
                add("World");
                add("!!!");
            }
        };
        List<String> list2 = new ArrayList<String>(list1);
        Set<String> set1 = new LinkedHashSet<String>() {
            {
                addAll(list1);
            }
        };
        Set<String> set2 = new LinkedHashSet<String>();
        set2.addAll(list1);
        Map<Integer, String> map1 = new LinkedHashMap<Integer, String>() {
            {
                put(1, "one");
                put(2, "two");
                put(3, "three");
            }
        };
        Map<Integer, String> map2 = new LinkedHashMap<Integer, String>();
        map2.putAll(map1);

        for (int i = 0; i < 10; i++) {
            long dbTimes = timeComparison(list1, list1)
                    + timeComparison(set1, set1)
                    + timeComparison(map1.keySet(), map1.keySet())
                    + timeComparison(map1.values(), map1.values());
            long times = timeComparison(list2, list2)
                    + timeComparison(set2, set2)
                    + timeComparison(map2.keySet(), map2.keySet())
                    + timeComparison(map2.values(), map2.values());
            if (i > 0)
                System.out.printf("double braced collections took %,d ns and plain collections took %,d ns%n", dbTimes, times);
        }
    }

    public static long timeComparison(Collection a, Collection b) {
        long start = System.nanoTime();
        int runs = 10000000;
        for (int i = 0; i < runs; i++)
            compareCollections(a, b);
        long rate = (System.nanoTime() - start) / runs;
        return rate;
    }

    public static void compareCollections(Collection a, Collection b) {
        if (!a.equals(b) && a.hashCode() != b.hashCode() && !a.toString().equals(b.toString()))
            throw new AssertionError();
    }
}

prints

double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 34 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
double braced collections took 36 ns and plain collections took 36 ns
Compendious answered 31/1, 2013 at 13:46 Comment(5)
No difference except that your PermGen space will evaporate if DBI is used excessively. At least, it will unless you set some obscure JVM options to allow class-unloading and garbage-collection of the PermGen space. Given Java's prevalence as a server-side language, the memory/PermGen issue warrants at least a mention.Kozhikode
@Kozhikode this is a good point. I admit that in 16 years of working on Java I have never worked on a system where you had to tune the PermGen (or Metaspace) For the systems I have worked on the size of the code was always kept reasonably small.Compendious
Shouldn’t the conditions in compareCollections get combined with || rather than &&? Using && seems not only semantically wrong, it counteracts the intention of measuring the performance, as only the first condition will be tested at all. Besides, a smart optimizer can recognize that the conditions will never change during the iterations.Outreach
@Kozhikode just as an update: since Java 8 the VM does not use any perm-gen anymore.Druggist
@AngelO'Sphere permgen is gone, but Metaspace is its successor (with some different behavior/limits) but the class definitions still exist in memory somewhere -- it's not free.Anthropography
G
16

To create sets you can use a varargs factory method instead of double-brace initialisation:

public static Set<T> setOf(T ... elements) {
    return new HashSet<T>(Arrays.asList(elements));
}

The Google Collections library has lots of convenience methods like this, as well as loads of other useful functionality.

As for the idiom's obscurity, I encounter it and use it in production code all the time. I'd be more concerned about programmers who get confused by the idiom being allowed to write production code.

Gastric answered 29/5, 2009 at 4:20 Comment(1)
Hah! ;-) I'm actually a Rip van Winkle returning to Java from the 1.2 days (I wrote the VoiceXML voice web browser at evolution.voxeo.com in Java). It's been fun learning generics, parameterized types, Collections, java.util.concurrent, the new for loop syntax, etc. It's a better language now. To your point, even though the mechanism behind DBI may seem obscure at first, the meaning of the code should be pretty clear.Impropriety
B
11

Efficiency aside, I rarely find myself wishing for declarative collection creation outside of unit tests. I do believe that the double brace syntax is very readable.

Another way to achieve the declarative construction of lists specifically is to use Arrays.asList(T ...) like so:

List<String> aList = Arrays.asList("vanilla", "strawberry", "chocolate");

The limitation of this approach is of course that you cannot control the specific type of list to be generated.

Byyourleave answered 29/5, 2009 at 3:59 Comment(2)
Arrays.asList() is what I would normally use, but you're right, this situation arises mainly in unit tests; real code would construct the lists from DB queries, XML, and so on.Impropriety
Beware of asList, though: the returned list does not support adding or removing elements. Whenever I use asList, I pass the resulting list into a constructor like new ArrayList<String>(Arrays.asList("vanilla", "strawberry", "chocolate")) to get around this problem.Fossilize
S
7

Double-brace initialization is an unnecessary hack that can introduce memory leaks and other issues

There's no legitimate reason to use this "trick". Guava provides nice immutable collections that include both static factories and builders, allowing you to populate your collection where it's declared in a clean, readable, and safe syntax.

The example in the question becomes:

Set<String> flavors = ImmutableSet.of(
    "vanilla", "strawberry", "chocolate", "butter pecan");

Not only is this shorter and easier to read, but it avoids the numerous issues with the double-braced pattern described in other answers. Sure, it performs similarly to a directly-constructed HashMap, but it's dangerous and error-prone, and there are better options.

Any time you find yourself considering double-braced initialization you should re-examine your APIs or introduce new ones to properly address the issue, rather than take advantage of syntactic tricks.

Error-Prone now flags this anti-pattern.

Supercool answered 12/4, 2017 at 20:24 Comment(6)
-1. Despite some valid points, this answer boils down to "How to avoid generating unnecessary anonymous class? Use a framework, with even more classes!"Avril
I would say it boils down to "use the right tool for the job, rather than a hack that can crash your application". Guava is a pretty common library for applications to include (you're definitely missing out if you're not using it), but even if you don't want to use it you can and should still avoid double-brace initialization.Supercool
And how exactly would a double brace initialization cause a meory leak?Druggist
@AngelO'Sphere DBI is an obfuscated way of creating an inner class, and so retains an implicit reference to its enclosing class (unless only ever used in static contexts). The Error-Prone link at the bottom of my question discusses this further.Supercool
I would say it is a matter of taste. And there is nothng really obfuscating about it.Druggist
I don't see how risking memory leaks is a matter of taste, but I don't really care to debate the point. If you truly feel that it's the best option available to you just be very careful how you use it.Supercool
S
6

There's generally nothing particularly inefficient about it. It doesn't generally matter to the JVM that you've made a subclass and added a constructor to it-- that's a normal, everyday thing to do in an object-oriented language. I can think of quite contrived cases where you could cause an inefficiency by doing this (e.g. you have a repeatedly-called method that ends up taking a mixture of different classes because of this subclass, whereas ordinary the class passed in would be totally predictable-- in the latter case, the JIT compiler could make optimisations that are not feasible in the first). But really, I think the cases where it'll matter are very contrived.

I'd see the issue more from the point of view of whether you want to "clutter things up" with lots of anonymous classes. As a rough guide, consider using the idiom no more than you'd use, say, anonymous classes for event handlers.

In (2), you're inside the constructor of an object, so "this" refers to the object you're constructing. That's no different to any other constructor.

As for (3), that really depends on who's maintaining your code, I guess. If you don't know this in advance, then a benchmark that I would suggest using is "do you see this in the source code to the JDK?" (in this case, I don't recall seeing many anonymous initialisers, and certainly not in cases where that's the only content of the anonymous class). In most moderately sized projects, I'd argue you're really going to need your programmers to understand the JDK source at some point or other, so any syntax or idiom used there is "fair game". Beyond that, I'd say, train people on that syntax if you have control of who's maintaining the code, else comment or avoid.

Shoulders answered 29/5, 2009 at 5:26 Comment(0)
R
3

I second Nat's answer, except I would use a loop instead of creating and immediately tossing the implicit List from asList(elements):

static public Set<T> setOf(T ... elements) {
    Set set=new HashSet<T>(elements.size());
    for(T elm: elements) { set.add(elm); }
    return set;
    }
Rigorism answered 29/5, 2009 at 4:50 Comment(3)
Why? The new object will be created in the eden space, and so only require two or three pointer additions to instantiate. The JVM may notice that it never escapes beyond the method scope and so allocate it on the stack.Gastric
Yeah, it's likely to end up more efficient than that code (although you can improve it by telling the HashSet a suggested capacity - remember load factor).Uzbek
Well, the HashSet constructor has to do the iteration anyway, so it's not going to be less efficient. Library code created for reuse should always strive to be the very best possible.Rigorism
I
3

Mario Gleichman describes how to use Java 1.5 generic functions to simulate Scala List literals, though sadly you wind up with immutable Lists.

He defines this class:

package literal;

public class collection {
    public static <T> List<T> List(T...elems){
        return Arrays.asList( elems );
    }
}

and uses it thusly:

import static literal.collection.List;
import static system.io.*;

public class CollectionDemo {
    public void demoList(){
        List<String> slist = List( "a", "b", "c" );
        List<Integer> iList = List( 1, 2, 3 );
        for( String elem : List( "a", "java", "list" ) )
            System.out.println( elem );
    }
}

Google Collections, now part of Guava supports a similar idea for list construction. In this interview, Jared Levy says:

[...] the most heavily-used features, which appear in almost every Java class I write, are static methods that reduce the number of repetitive keystrokes in your Java code. It's so convenient being able to enter commands like the following:

Map<OneClassWithALongName, AnotherClassWithALongName> = Maps.newHashMap();

List<String> animals = Lists.immutableList("cat", "dog", "horse");

7/10/2014: If only it could be as simple as Python's:

animals = ['cat', 'dog', 'horse']

2/21/2020: In Java 11 you can now say:

animals = List.of(“cat”, “dog”, “horse”)

Impropriety answered 22/10, 2009 at 6:6 Comment(0)
S
3

I was researching this and decided to do a more in depth test than the one provided by the valid answer.

Here is the code: https://gist.github.com/4368924

and this is my conclusion

I was surprised to find that in most of the run tests the internal initiation was actually faster (almost double in some cases). When working with large numbers the benefit seems to fade away.

Interestingly, the case that creates 3 objects on the loop loses it's benefit rans out sooner than on the other cases. I am not sure why this is happening and more testing should be done to reach any conclusions. Creating concrete implementations may help to avoid the class definition to be reloaded (if that's what's happening)

However, it is clear that not much overhead it observed in most cases for the single item building, even with large numbers.

One set back would be the fact that each of the double brace initiations creates a new class file that adds a whole disk block to the size of our application (or about 1k when compressed). A small footprint, but if it's used in many places it could potentially have an impact. Use this 1000 times and you are potentially adding a whole MiB to you applicaiton, which may be concerning on an embedded environment.

My conclusion? It can be ok to use as long as it is not abused.

Let me know what you think :)

Skolnik answered 24/12, 2012 at 12:17 Comment(1)
That’s not a valid test. The code creates objects without using them which allows the optimizer to elide the entire instance creation. The only remaining side-effect is the advancing of the random number sequence whose overhead outweighs anything else in these tests anyway.Outreach
U
3

While this syntax can be convenient, it also adds a lot of this$0 references as these become nested and it can be difficult to step debug into the initializers unless breakpoints are set on each one. For that reason, I only recommend using this for banal setters, especially set to constants, and places where anonymous subclasses don't matter (like no serialization involved).

Urger answered 18/6, 2014 at 20:46 Comment(0)
D
2
  1. This will call add() for each member. If you can find a more efficient way to put items into a hash set, then use that. Note that the inner class will likely generate garbage, if you're sensitive about that.

  2. It seems to me as if the context is the object returned by new, which is the HashSet.

  3. If you need to ask... More likely: will the people who come after you know this or not? Is it easy to understand and explain? If you can answer "yes" to both, feel free to use it.

Departmentalism answered 29/5, 2009 at 3:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.