What needs to know about Array after Generics was introduced since JDK 5
What changes will happen when Generics comes to live with array? Let's find out together.
The limitation over generic array creation
With JDK 5 or newer version, when you write the following code snippet in your Java IDE, the error on compile time will be immediately highlighted
List<Integer>[] arrayOfIntegerList = new List<Integer>[2]; // compile-time errorThe error shows generic array of List<Integer> can’t be created.
Why the compile-error happens
To understand this, we need to know Array in Java is covarient which means T[] is the subtype of S[] whenever T is subtype of S. So the following code snippet is legal on compile time but will cause run time exception on third line
Object[] strings = new String[2];
strings[0] = “hello”;
strings[1] = 100; // An ArrayStoreException is thrown.Because of substitution principle, the assignment on the first line is legal as String[]is subtype of Object[]. When an array is allocated, it is tagged with reified type ( a reified type is a type whose type information is fully available at run time) which is String in this case. Every time an array element is assigned like the second and third line , the reified type will be checked against the actual type of assigned value which is an Integer in this instance, ArrStoreExceptionwill be raised if mismatch spotted.
In contrast to array, the generics with type parameter T is invarient which doesn’t allow the subtype substitution as the following code snippet shows
List<String> strings = Arrays.asList(“hello”);
List<Object> objects = strings; //compile-time error
objects.add(100);Either way, its impossible to assign the Integer to array or collection which keeps element of type String. The difference is array raises the exception at run time but generics detects the exception at compile time.
Its well known the fundamental purpose of generics is to improve the type safety in Java and avoid the ClassCastExceptionby forcing compile-time type check. If we mix the array with generics, it will go against the intention generics were initially introduced as the code snippet below shows
public class GenericArray {
class Box<T> {
T x;
Box(T x) {
this.x = x;
}
}
public static void main(String[] args) {
Box<String>[] stringBoxArray = new Box<String>[3]; //1, unable to compile actually
Object[] objectArray = stringBoxArray; //2
objectArray[0] = new Box<Integer>(3); //3
String s = stringBoxArray[0].x; //4
}
}If the line 1 were successful, line 2 will be legal because array is covarient. Line 3 saves the Box instance of type Integer into the array, it will not generate ArrayStoreExceptionbecause Box<String>has no difference with Box<Integer>on run time after the type erasure. Line 4 retrieves the box element and expect the value of type String to be returned, but it fails with ClassCastException.
In order to prevent the ClassCastException from happening, Java generics generates the compile time error on line 1.
Refiable types fit array
Being covarient, array needs component type to be reified at run time. The following types in java are all reifiable and can be used to define component type of array.
- Primitive type, such as
int - Non-parameterized class and interface, such as
Integer,String,Number - Parameterized type with unbounded wildcard,such as
List<?>,ArrayList<?>.
Although legal, but is rarely useful to create array of unbounded wildcard as we can only insert null intoList<?> - Raw type,such as
List,ArrayList - Array whose component type is reifiable. Such as
int[],Integer[],List<?>,List[]
Is Array still useful?
Its recommended we prefer type parameterized collections to array as generics provide type safety at compile time and collection classes offer richer set of methods than array to operate with elements.
But sometimes, array will be more efficient with primitive types as no boxing/unboxing involved upon element access.
References
- Effective Java 3rd edition
- https://docs.oracle.com/javase/tutorial/java/generics/
- Java Generics and Collections
