Don’t use the double-brace initialization trick
Introduction
Java unfortunately does not have a convenient syntax for initializing collections. There’s a trick that developers sometimes use, the double-brace initialization trick, to make initializing for example a Map more convenient. This trick has obvious and less obvious disadvantages and you should not use it. In this post I explain exactly why you should not use it and what you can do instead.
When you want to initialize a Map, for example, you need a sequence of statements:
Map<Integer, String> names = new HashMap<>(); names.put(1, "one"); names.put(2, "two"); names.put(3, "three");
This is especially a problem when the Map is a static final member; you’d need a static initializer block to initialize it, making the code even more verbose:
public class Example { private static final Map<Integer, String> NAMES; static { Map<Integer, String> map = new HashMap<>(); map.put(1, "one"); map.put(2, "two"); map.put(3, "three"); NAMES = Collections.unmodifiableMap(map); } // ... }
Wouldn’t it be nice if you could do this with an initialization expression, for example using special syntax which might look something like this:
private static final Map<Integer, String> NAMES = #{ 1 : "one", 2 : "two", 3 : "three" };
The idea of literals for collections has been proposed already years ago (see JEP-186), but hasn’t yet been implemented and, as far as I know, is not currently on the roadmap for Java 9 or another future Java version.
The trick and why it’s bad
One trick that developers sometimes use to make this more convenient is the double-brace initialization trick. Let’s first have a look at how that looks, and then discuss what is actually happening:
private static final Map<Integer, String> NAMES = new HashMap<Integer, String>() {{ put(1, "one"); put(2, "two"); put(3, "three"); }};
How does this work?
What happens here is that on the right side of the =, an anonymous subclass of class HashMap is created. This anonymous subclass has an instance initialization block – the block of code containing the put statements, surrounded by the innermost pair of { and }.
This works, but it has a number of disadvantages.
First of all, it is a trick, which means that the code is obscure – if you’ve never seen this before, it’s hard to quickly understand what is happening. Especially less experienced developers will be baffled by the {{ and }} and might think that these are tokens with special significance. Also, instance initializers are a seldom used Java feature, which many developers won’t have seen before.
Second, there’s an anonymous class here, which means that the compiler will generate an extra *.class file and that at runtime this extra class has to be loaded by the JVM. This puts an extra burden on the JVM and will cost more memory.
The third disadvantage is less obvious, but is worse than the other two. To illustrate it, let’s look at the following example.
public class Document { private final String text; public Document(String text) { this.text = text; } public int countWord(String word) { return ...; // Implementation omitted } public Map<String, Integer> countArticles() { return new HashMap<String, Integer>() {{ put("a", countWord("a")); put("an", countWord("an")); put("the", countWord("the")); }}; } }
The anonymous subclass of HashMap returned by the countArticles method has a hidden reference to the enclosing instance of class Document. You can see this in the instance initialization block: there the countWord method is called on the containing Document object – this is only possible if the instance of the anonymous subclass has a reference to the Document object. This hidden reference is essentially an automatically-generated member variable in the anonymous subclass which refers to the Document object.
This means that as long as any live thread in the program holds a reference to the Map object returned by countArticles, the enclosing Document instance is not eligible for garbage collection, even if no thread holds a direct reference to the Document object – that’s a memory leak.
It also causes a problem with serialization. If you try to serialize the Map returned by countArticles, you will get a NotSerializableException because the Document that the anonymous subclass refers to is not Serializable. If you would try to solve this by making class Document implement Serializable, you’d only be making the problem worse, because the Document would unnecessarily be serialized, and a client which would try to deserialize the object would get a ClassNotFoundException if the client wouldn’t have class Document in its classpath.
So, those are good reasons to not use the double-brace initialization trick!
What to do instead
One possible solution is to create a MapBuilder class, for example like this:
import java.util.HashMap; import java.util.Map; public final class MapBuilder<K, V> { private final Map<K, V> map = new HashMap<>(); public MapBuilder<K, V> put(K key, V value) { map.put(key, value); return this; } public Map<K, V> build() { return map; } }
And an example of what it looks like to use this:
public class Example { private static final Map<Integer, String> NAMES = new MapBuilder<Integer, String>() .put(1, "one") .put(2, "two") .put(3, "three") .build(); // ... }
This is not more code than when you’d use the double-brace initialization trick and does not have the disadvantages.
It’s getting better in Java 9
It looks like things are going to get better in the future. Java 9 will not have syntax for collection literals, but it looks like another proposal will most likely be implemented in Java 9. Convenience methods for creating collections will be added (see JEP-269), so that you can write your code like this:
private static final Map<Integer, String> NAMES = Map.of(1, "one", 2, "two", 3, "three");
If you’re using Java 8 or older, another solution besides creating a MapBuilder class is to implement the JEP-269 helper methods yourself – it isn’t very complicated. Just don’t use the double-brace initialization trick when you do this!
I mostly use this for quickly initializing a piece of data in a unit test, and the only applicable argument against its use in this situation is that it may look a bit obscure if one sees it for a first time, IMHO.
Very informative! Have been using this all these days for doing things quick. I did n’t even know that I was making this worse
Found your blog through pluralsight. Thanks for this article! It’s really helpful. I’m well informed now not to use it regularly.