Incompatible Java 8 change in the Collections classes
Suppose that we want to implement a HashMap (let’s call it OrderedHashMap where the values can also retrieved by their index, where the index refers to the order in which the key/value pairs have been inserted. The following unit test shows how such a class could be used:
@Test public void testPutGet() { OrderedHashMap<string, string=""> ohm = new OrderedHashMap<>(); ohm.put("Z", "LetterZ"); // 0 ohm.put("A", "LetterA"); // 1 ohm.put("D", "LetterD"); // 2 ohm.put("K", "LetterK"); // 3 assertEquals(ohm.get(0), "LetterZ"); assertEquals(ohm.get(3), "LetterK"); } </string,>
The straight forward implementation of the class OrderedHashMapis to inherit from one of the existing Collection classes and simply implement the additional functionality, e.g. like
package com.example; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; public class OrderedHashMap<k, v=""> extends LinkedHashMap<k, v=""> { private static final long serialVersionUID = 6354582314971513369L; private List<v> items = new ArrayList<>(); public V get(int index) { return items.get(index); } @Override public V put(K key, V value) { items.add(value); return super.put(key, value); } } </v></k,></k,>
With this code, the above unit test succeeds. Lets add an additional unit tests for one of the other methods inherited from LinkedHashMap, putAll():
@Test public void testPutAll() { OrderedHashMap<string, string=""> ohm = new OrderedHashMap<>(); ohm.put("Z", "LetterZ"); // 0 ohm.put("A", "LetterA"); // 1 ohm.put("D", "LetterD"); // 2 ohm.put("K", "LetterK"); // 3 OrderedHashMap<string, string=""> ohm2 = new OrderedHashMap<>(); ohm2.putAll(ohm); assertEquals(ohm2.get(0), "LetterZ"); assertEquals(ohm2.get(3), "LetterK"); } </string,></string,>
This unit test also succeeds and shows that we can copy the elements from one OrderedHashMap to a second one, using putAll(). Unless we switch to Java 8. If we run the above sample with the Java 8 runtime, the test fails with an exception:
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0 at java.util.ArrayList.rangeCheck(Unknown Source) at java.util.ArrayList.get(Unknown Source) at com.example.OrderedHashMap.get(OrderedHashMap.java:13) at com.example.test.OrderedHashMapTest.testPutAll(OrderedHashMapTest.java:37) ...
This is an issue I came across when I was trying to translate a grammar with ANTLR (not using the latest version) – see also https://github.com/antlr/antlr4/issues/337. The background is that the LinkedHashMap class (or one of its super classes) changed the implementation of putAll() so that it no longer calls put() to insert the new elements in the destination map. Hence, our own implementation is no longer able to catch this call to update the List at the same time a new key/value pair is added to the map. If you do similar things, you should check if there are such differences between the Java 7 and the Java 8 runtime which might affect your code. In general, this is a good example to Favor Composition over Inheritance in Java and Object Oriented Programming. The main problem is that the sub class depends on the behaviour of the superclass, and hence becomes fragile (and breaks when the superclass behaviour changes in an incompatible way). Collections are always a bad candidate for subclassing – instead, create a new class which implements the desired interfaces and then delegates to the necessary Collection classes. This is more effort at the beginning (to implement all the necessary interface methods), but makes the class much more resistent against modifications in the runtime classes. See also Effective Java, Second Edition: Item 16: Favor composition over inheritance.