5 Important Points about Java Generics
Generics allows a type or method to operate on objects of various types while providing compile-time type safety, making Java a fully statically typed language.
Join the DZone community and get the full member experience.
Join For FreeGenerics are one of the most controversial Java language features. Generics allows a type or method to operate on objects of various types while providing compile-time type safety, making Java a fully statically typed language. In this article, I am going to talk about five things that every Java developer should know about Generics.
Generics are implemented using Type Erasure
In Java a class or an interface can be declared to define one or more type parameters and those type parameters should be provided at the time of object construction. For example,Generics does not support sub-typing
Generics does not support sub-typing which means that List is not considered to be a sub-type of List, where S is a subtype of T. For example, You can't create Generic Arrays
You can't create generic arrays as shown below because arrays carry runtime type information about the type of elements . Arrays uses this information at runtime to check the type of the object it contains and will throw ArrayStoreException if the type does not match. But with Generics type information gets erased and the array store check will succeed in cases where it should fail. Use of wildcards with extends or super to increase API flexibility
There are times that you need to work not only with T but also with sub types of T. For example, the addAll method in the Collection interface which adds all the elements in the specified collection to the collection on which it is called. addAll method has the signature Use of Multiple Bounds
Multiple bounds is one of the generics features which most developer do not know. It allows a type variable or wildcard to have multiple bounds. For example, if you to define constraint such as that the type should be a Number and it should implement Comparable.
List<Long> list = new ArrayList<Long>();list.add(Long.valueOf(1));list.add(Long.valueOf(2));
In the example shown above a List is created which can only contain elements of type Long and if you try to add any other type of element to this list, it will give you compile time error. It helps detect errors at compile time and makes your code safe. Once this piece of code is compiled ,the type information is erased resulting into similar byte code as we would have got if the same piece of code was written using Java 1.4 and below. This results in binary compatibility between different versions of Java. So, a List or List<> are all represented at run-time by the same type, List. List<Number> numbers = new ArrayList<Integer>(); // will not compile
The piece of code shown above will not compile because if it compiles than type safety can't be achieved. To make this more clear, lets take the following piece of code shown below where at line 4 we are assigning a list of long to a list of numbers. This piece of code does not compile because if it could have compiled we could add a double value in a List of longs. This could have resulted in ClassCastException at runtime and type safety could not be achieved.List<Long> list = new ArrayList<Long>();list.add(Long.valueOf(1));list.add(Long.valueOf(2));List<Number> numbers = list; // this will not compilenumbers.add(Double.valueOf(3.14));
T[] arr = new T[10];// this code will not compile
You can't even create Arrays of Generic classes of interfaces. For example, the code shown below does not compile. List<Integer>[] array = new List<Integer>[10]; // does not compile
Arrays behave differently from the collections because arrays are covariant by default, which means that S[] is a subtype of T[] whenever S is a subtype of T, where as Generics does not support covariance. So, if the above code had compiled then the array store check would succeed in cases where it should fail. For example, List<Integer>[] ints = new List<Integer>[10]; // does not compileObject[] objs = ints;List<Double> doubles = new ArrayList<Double>();doubles.add(Double.valueOf(12.4));objs[0] = doubles; // this check should fail but it succeed
If the generic arrays were allowed, then we could assign ints array to an object array because arrays are covariant. After that we could add a List of double to the obj array. We will expect that this will fail with ArrayStoreException because we are assigning List of double to an array of List of integers. But the JVM cannot detect type mismatch because the type information gets erased. Hence the array store check succeeds, although it should have failed. boolean addAll(Collection<? extends E> c)
This ? extends E makes sure that you can not only add collection of type E but also of subtype of E. ? is called the wildcard and ? is said to be bounded by E. So,if we create a List of Number then we can not only add List of number but we can also add List of Integer or any other subtype of Number. List<Number> numbers = new ArrayList<Number>();ArrayList<Integer> integers = new ArrayList<Integer>();ArrayList<Long> longs = new ArrayList<Long>();ArrayList<Float> floats = new ArrayList<Float>();numbers.addAll(integers);numbers.addAll(longs);numbers.addAll(floats);
So far we have covered the use of extends with wildcards and we saw that API became more flexible after using extends . But where will we use super? Collections class has a method called addAll which add all the specified elements to the specified collection. It has following signature public static <T> boolean addAll(Collection<? super T> c, T... elements) ;
In this method you are adding elements of type T to the collection c. super is used instead of extends because elements are added into the collection c whereas in the previous example of Collection interface addAll method elements were read from the collection . In the Effective Java book, Joshua Bloch calls this PECS. PECS stands for producer extends, consumer super.It proves very helpful whenever you are confused about whether you should use extends or super.
public static <T extends Number & Comparable<? super T>> int compareNumbers(T t1, T t2){return t1.compareTo(t2);}
It makes sure that you can only compare two numbers which implement Comparable. Multiple bounds follows the same constraints as followed by the a class i.e. T can't extend two classes ,you have to first specify the class then the interface, and T can extend any number of interfaces. public static <T extends String & Number > int compareNumbers(T t1, T t2) // does not work..can't have two classespublic static <T extends Comparable<? super T> & Number > int compareNumbers(T t1, T t2) // does not work..public static <T extends CharSequence & Comparable<T>> int compareNumbers(T t1, T t2)// works..multiple interfaces
Opinions expressed by DZone contributors are their own.
Comments