Monday, December 24, 2012

Double Brace Initialization in Java: Pros and Cons

A month ago I’ve encountered an interesting idiom for the Java programming language – double brace initialization. I suppose it emerged because the language currently doesn’t have convenient syntax for collection literals. The double brace initialization technique do adds some syntactic sugar to the Java but there are a couple of caveats you should be aware of.

Double Brace Initialization in Action

The easiest way to explain the idiom is to show an example. Suppose you are writing a unit-test and want to make sure your DB access logic returns correct entities. You know that primary keys of entities which should be returned are 1, 2, 3, 4 and 5. The following code can be used to achieve this:
@Test
public void test() {
    final Set<Long> expectedIds = new HashSet<Long>();

    expectedIds.add(1L);
    expectedIds.add(2L);
    expectedIds.add(3L);
    expectedIds.add(4L);
    expectedIds.add(5L);

    final Set<Long> actualIds = // retrieve actual ids here

    Assert.assertEquals(expectedIds, actualIds);
}
Code used to initialize expectedIds looks pretty verbose. It can be simplified a bit using double brace initialization. The previous example could be rewritten like this:
@Test
public void testDoubleBraceInitialization() {
    final Set<Long> expectedIds = new HashSet<Long>() {{
        add(1L);
        add(2L);
        add(3L);
        add(4L);
        add(5L);
    }};

    final Set<Long> actualIds = // retrieve actual ids here

    Assert.assertEquals(expectedIds, actualIds);
}
As you can see double brace initialization makes collection instantiation a bit more compact. Moreover the code which fills the collection with values is part of the same expression which creates an instance of the HashSet class.

At first glance double brace initialization might look like a magic, but actually it is very simple. There are two Java language constructs involved in it: anonymous class and instance initializer. The outer pair of braces defines an anonymous class which extends HashSet<Long> and the inner pair of braces defines an instance initializer which is a common approach to initialize anonymous class instances.

As you might guess, this technique is not limited to collection classes. It can be used to initialize any kind of object. Another interesting example can be built by applying double brace initialization idiom to GUI objects:
JPanel panel = new JPanel() {{
    setLayout(...);
    setBorder(...);
    add(new JLabel("Submit"));
    add(new JButton("Submit"));
    add(new JPanel() {{
        setLayout(...);
        setBorder(...);
        add(new JSpinner());
        add(new JTable());
    }});
}};
As you can see, double brace initialization technique might facilitate creation of more compact code, but it comes at a price. There are a couple of caveats you should be aware of to make a deliberate decision whether to use this idiom or not.
  1. Every time you use double brace initialization you create a new class. This means you will have one additional .class file for each idiom application. Here is an example to make this more specific:
    public class DoubleBraceInitializationExample {
        public static void main(String[] args) {
            final Set<Integer> set1 = new HashSet<Integer>() {{
                add(1);
                add(2);
                add(3);
            }};
    
            final Set<Integer> set2 = new HashSet<Integer>() {{
                add(1);
                add(2);
                add(3);
            }};
    
            final List<Integer> list1 = new ArrayList<Integer>() {{
                add(4);
                add(5);
                add(6);
            }};
    
            final List<Integer> list2 = new ArrayList<Integer>() {{
                add(4);
                add(5);
                add(6);
            }};
    
            System.out.println(set1);
            System.out.println(set2);
            System.out.println(list1);
            System.out.println(list2);
        }
    }
    
    This program contains four applications of double brace initialization. After you compile this example you end up with 5 classes represented by the following .class files:

    DoubleBraceInitializationExample.class
    DoubleBraceInitializationExample$1.class
    DoubleBraceInitializationExample$2.class
    DoubleBraceInitializationExample$3.class
    DoubleBraceInitializationExample$4.class

    This doesn’t cause much harm in testing code, but overusing double brace initialization in a large project which is intended for server-side execution might bloat your application with thousands of small classes and increase deployment time as a consequence.
  2. Double brace initialization cannot be applied to final classes. Pretty obvious point the compiler will tell you about immediately. Since this technique uses inheritance to instantiate anonymous class instances the superclass should allow doing this and hence it cannot be final.
  3. Additional precautions should be taken if an object initialized with double brace initialization is going to be compared with other objects using equals(). If implementation of method equals() in superclass doesn’t accept instances of subclasses then double brace initialization can introduce subtle bugs and inconsistencies. Let’s look at an example. Here is a simplistic Person class:
    public class Person {
    
        private String firstName;
        private String lastName;
        private int age;
    
        // getters and setters are omitted
    
        @Override
        public boolean equals(Object o) {
            if (o instanceof Person) {
                Person other = (Person) o;
                return firstName.equals(other.getFirstName()) &&
                        lastName.equals(other.getLastName()) &&
                        age == other.getAge();
            }
    
            return false;
        }
    
        @Override
        public int hashCode() {
            int result = firstName.hashCode();
            result = 31 * result + lastName.hashCode();
            result = 31 * result + age;
            return result;
        }
    }
    
    In this implementation of Person class method equals() uses instanceof operator and hence accepts subclasses for equality check. Instances of Person class initialized with double brace initialization will behave as expected in this case. The following program illustrates this:
    public class DoubleBraceInitializationExample {
        public static void main(String[] args) {
            Person person1 = new Person() {{
                setAge(26);
                setFirstName("John");
                setLastName("Doe");
            }};
    
            Person person2 = new Person() {{
                setAge(26);
                setFirstName("John");
                setLastName("Doe");
            }};
    
            System.out.println(person1.equals(person2));
        }
    }
    
    This program prints true as expected. Another popular implementation of equals() method checks class objects for equality instead of using instanceof operator effectively disallowing instances of subclasses to be passed for comparison. Let’s look at this alternative implementation in the Person class context:
    public class Person {
    
        private String firstName;
        private String lastName;
        private int age;
    
        // getters and setters are omitted
    
        @Override
        public boolean equals(Object o) {
            if (o != null && o.getClass().equals(Person.class)) {
                Person other = (Person) o;
                return firstName.equals(other.getFirstName()) &&
                        lastName.equals(other.getLastName()) &&
                        age == other.getAge();
            }
    
            return false;
        }
    
        @Override
        public int hashCode() {
            int result = firstName.hashCode();
            result = 31 * result + lastName.hashCode();
            result = 31 * result + age;
            return result;
        }
    }
    
    In this implementation of Person class method equals() accepts Person instances for comparison but instances of subclasses are not allowed. Double brace initialization will not work as intended in this case. The same illustrative program can be used to verify this:
    public class DoubleBraceInitializationExample {
        public static void main(String[] args) {
            Person person1 = new Person() {{
                setAge(26);
                setFirstName("John");
                setLastName("Doe");
            }};
    
            Person person2 = new Person() {{
                setAge(26);
                setFirstName("John");
                setLastName("Doe");
            }};
    
            System.out.println(person1.equals(person2));
        }
    }
    
    This time the output will be false. As you might guess person1 and person2 objects will not work as expected with any class that relies on equals() and hashCode() contract (e.g. HashSet, HashMap, etc.).

    The bottom line is that you should be extra careful when using objects created with double brace initialization with classes that rely on equals() and hashCode() contract. The best approach in this case is to refrain from using double brace initialization at all. The benefits are small compared to potential headaches.
  4. Double brace initialization can lead to nasty memory leaks. This idiom uses anonymous classes which are never static according to JLS. Hence anonymous class always keeps a reference to its enclosing object. Let’s look at an example. Assume you need to implement an Employee class and you want to create instances of this class using Builder design pattern to emulate named parameters. One possible implementation might look like this:
    public class Employee {
    
        private String firstName;
        private String lastName;
        private String address;
    
        private Employee() {
        }
    
        public static class Builder {
    
            private String firstName;
            private String lastName;
            private String address;
    
            public Builder firstName(String firstName) {
                this.firstName = firstName;
                return this;
            }
    
            public Builder lastName(String lastName) {
                this.lastName = lastName;
                return this;
            }
    
            public Builder address(String address) {
                this.address = address;
                return this;
            }
    
            public Employee build() {
                return new Employee() {{
                    setFirstName(firstName);
                    setLastName(lastName);
                    setAddress(address);
                }};
            }
        }
    
        // public getters are omitted
    
        protected void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        protected void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        protected void setAddress(String address) {
            this.address = address;
        }
    }
    
    This implementation introduces memory leak because build() method applies double brace initialization to create Employee object, hence it uses anonymous class which keeps a reference to the enclosing Builder instance. A typical usage of the static inner Builder class implies throwing its instance away right after the product is created. But in this case a reference to the enclosing Builder instance will be kept by anonymous subclass of Employee as long as the product is strongly reachable. This is the consequence of double brace initialization application. The following program illustrates a typical usage of the builder:
    public class DoubleBraceInitializationExample {
        public static void main(String[] args) {
            Employee employee = new Employee.Builder()
                    .address("49 Featherstone Street, London")
                    .firstName("John")
                    .lastName("Doe")
                    .build();
    
            System.out.println(employee.getFirstName());
            System.out.println(employee.getLastName());
            System.out.println(employee.getAddress());
        }
    }
    
    This program will work as expected, but will take additional memory under the hood without any good reason.

Alternative Techniques

As you can see from the previous discussion double brace initialization do facilitates more compact code creation, but it can easily introduce a lot of confusion and subtleties into your program. Fortunately several alternative techniques exist which give you almost the same benefits avoiding all the headaches double brace initialization can create. For instance, if you have a known set of values and you need to add them to a List or a Set (as in the unit-testing example in the beginning of this post) you could easily do it like this:
Set<Integer> set = new HashSet<Integer>(Arrays.asList(1, 2, 3));
List<Integer> list = new ArrayList<Integer>(Arrays.asList(1, 2, 3));
This code is even more compact than double brace initialization and it doesn’t prone to any of the problems related to double brace initialization.

For Map it is not so easy. At the time of this writing standard JDK facilities don’t provide a convenient way of creating maps, but a couple of third-party libraries exist which do have means for this. For example Apache Commons Lang and its ArrayUtils.toMap() method can be used to simplify maps creation:
@SuppressWarnings("unchecked")
Map<Integer, String> map1 = (Map) ArrayUtils.toMap(new Object[][] {
        {1, "one"},
        {2, "two"},
        {3, "three"}
});
This also doesn’t look as good as it could. Usage of annotation and a cast just to create a map still overcomplicates the code. One way to improve on this is to introduce a static method and hide the complexity there:
@SuppressWarnings("unchecked")
private static <K, V> Map<K, V> mapOf(Object[] args) {
    return (Map) ArrayUtils.toMap(args);
}
In this case map creation can be as simple as the following:
Map<Integer, String> map2 = mapOf(new Object[][] {
        {1, "one"},
        {2, "two"},
        {3, "three"}
});
This approach doesn’t look perfect too, but it is probably the best thing we can squeeze from the current version of Java (Java 7 at the time of this writing).

Summary

So, let’s recap what we have. Double brace initialization is a nice Java language trick which can facilitate writing more compact code, but it can easily introduce a lot of confusion and subtleties in your program. Also it creates one additional .class file per usage and that can easily bloat your project with lots of additional classes. But if it is not overused and applied judiciously occasionally it can be useful (e.g. in testing code or in GUI components interconnection building).

Thanks for reading,
See you soon!

No comments:

Post a Comment