In Hidden Features of Java the top answer mentions Double Brace Initialization, with a very enticing syntax:
Set flavors = new HashSet
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){
}
};
}
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.
It seems to me as if the context is the object returned by new
, which is the HashSet
.
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.
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.
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.
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 :)
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.