Why you shouldn't use the double brace initializer

I recently came across something I never really considered doing in my code- using instance initializers to instantiate collections. This seemed like a good idea at first seeing how there hasn't really been a convenient way of instantiating and initializing collections without having to manually populate the list.

List<String> listOfLetters = new ArrayList<>();
listOfLetters.add("A");
listOfLetters.add("B");
listOfLetters.add("C");

A couple of solutions do a great job of proving a fix for this like the handy Arrays class with the static method asList.

List<String> listOfLetters = Arrays.asList("A", "B", "C");

However, this returns an immutable list[1]. Attempting to change it, throws an UnsupportedOperationException.

The double brace initializer uses the following syntax taking advantage of instance initialization.

List<String> listOfLetters = new ArrayList<String>() {{
        for (int i = 65; i <= 100; i++) add(String.valueOf((char) i));
}};

This little bit of convenience may look enticing, but as with all things - comes at a bit of a price.

Instance initializers

The instance initializer block[1:1] can be used as an alternative to using the constructor, or instance variable initializer to initialize instance members in an object.

/**
* Sample class demonstrating
* instance initializers
*/
class Sample {
    /**
    * Constructor
    */
    public Sample(){		
        System.out.println("Mjolnir! To me!");
    }

    /**
    * Instance initializer
    */
    {
        System.out.println("I am the Odinson!");
    }
	
    public static void main (String[] args){
        new Sample();
    }
}

Executing this code delivers this as the output.

I am the Odinson!
Mjolnir! To me!

What makes instance initializer blocks so interesting is that they are copied into all constructors in the class after super() and before any existing code in the constructor by the compiler. They are a great way of sharing code between constructors without having to do a lot of copy pasting.

...[Instance initializer blocks] are copied into all constructors in the class after super() and before any existing code in the constructor

This means that after compilation, the example constructor above is effectively the same as writing,

public Sample(){	
    super();
    System.out.println("I am the Odinson!");
    System.out.println("Mjolnir! To me!");
}

Instance initializers are also useful in anonymous inner classes where you cannot include a constructor.

//Anonymous inner class is used to initialize mjolnir
Hammer mjolnir = new Hammer() {
    {
       	//What to inject into constructor
    }

    public void callForthLightning(boolean isWorthy) {
                
    }
};

The problem

Despite the way they look, anonymous inner classes are, not surprisingly, just classes. This means that when they compile, a *.class file is generated and at runtime are loaded by the class loader.

Consider the sample code,

import java.util.ArrayList;
import java.util.List;

public class Sample {
	public Sample() {

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

	public static void main(String[] args) {
		new Sample();
	}
}

After compilation, the java compiler outputs Sample.class, Sample$1.class, Sample$2.class, Sample$3.class. Classes containing the $ followed by a number are the 3 anonymous inner classes running through the compiler. This means that every time the double brace initializer is used, a new anonymous inner class is created which will be compiled into a separate *.class file which will later be invoked by the class loader at runtime reducing the overall performance of your application.

... every time the double brace initializer is used, a new anonymous inner class is created which will be compiled into a separate *.class file ...

But wait! There's more! Non static anonymous inner classes keep a reference to their enclosing instance. This could prevent the garbage collector from freeing up the occupied memory long after it is no longer in use. It also means that in order to serialize the collection, you will have to serialize the entire enclosing class.

Non static anonymous inner classes keep a reference to their enclosing instance.

To add to this, anonymous inner classes don't support the diamond operator which means - no type inference. This means that code such as,

List<String> awesomeDaysOfTheWeek = new ArrayList<>() {{
        add(Friday);
        add(Saturday);
        add(Sunday);
}};

will fail to compile.

Conclusion

Well the problems here ultimately come down more towards why we should avoid anonymous inner classes wherever we can rather than just a problem with the construct in the context of it's use within collections.

So the big question is, what do i use instead? To that I say, whatever works best for you. Whether that's Commons, Arrays, the Google Collections API or even alternative JVM languages with better semantics for instantiating collections.

... what do i use instead? ... whatever works best for you.

While the double brace initializer may yet prove itself to be an appropriate solution to problems in certain contexts, what know for sure right now is that knowing about it's potential problems will help in making a well informed decision. So at the end of the day, it's just another tool in the toolbox.

Got a comment? Join the conversation on reddit.


  1. https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html ↩︎ ↩︎