Generics in Java
Generics is essential features in java programming language that makes the collections type-safe in compile time. In this blog, we will be exploring what generics are, how they work, and why they are essential for writing robust and maintainable code.
Prior to jdk 1.5, a collection is consider of a collection of Objects and down casting was required to retrieve elements.
List words = new ArrayList();
words.add("Hello");
words.add(" world!");
String s = ((String)words.get(0)) + ((String)words.get(1));
System.out.print(s); //output: Hello world!
In jdk 1.5, generic parameters were added to the declaration of collection classes, so that the above code could be rewritten as follows
List<String> words = new ArrayList<String>();
words.add(“Hello”);
words.add(“ world!”);
String s = words.get(0) + words.get(1);
System.out.print(s); //output: Hello world!
The class with declaration Class ArrayList<T> { . . . } is called a generic class, and T is called a type variable or type parameter.
Benefits of Generics
- Type checks at compile time: We can prevent the type related issue in compile time.
- Elimination of down casts: Programmer don’t need to do unnecessary casting.
- Cleaner generic algorithms: It refers to simplicity, efficiency, flexibility and avoiding unnecessary complexity.
- Improved readability: Generics make the code more readable by making it clear what types of objects are being used
- Reusability: We can use different kind of type without the code repetition.
How Java Implements Generics (Type Erasure):
Type erasure is the process that is used in compile time to implement the generics of java. This process just replace the generic type with actual class and casting if required. compiler ensures that no extra classes are created and there is no runtime overhead.
- Replace all type parameters in generic types with their bounds (? extends T, ? super T, Number, String and so on)or unbounded (? means ? extends Object ).
- The compiled code for generics will carry out the down casting if required.
- Generate bridge methods to preserve polymorphism in extended generic types.
The Downside of Generics
- Generic Subtyping Is Not Covariant: Number[] is a subclass of Integer[]. So Array subtyping is covariant. But ArrayList<Number> is not a subclass of ArrayList<Integer>. The reason is that every array knows its element type during runtime, while generic collection doesn’t because of type erasure.
List<Integer> ints = new ArrayList<Integer>();
ints.add(1);
ints.add(2);
List<Number> nums = ints; //compiler error
- Cannot Create Instances of Type Parameters: You cannot create an instance of a type parameter. For example, the following code causes a compile-time error:
public static <T> void append(List<T> list) {
T elem = new T(); // compile-time error
list.add(elem);
}
. Cannot Create Arrays of Parameterized Types: You cannot create arrays of parameterized types. For example, the following code does not compile:
List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile-time error
. Don’t support primitive types: Generic class is compiled by type eraser that’s why they use wrapper class for backward compatibility see details.
Wildcard:
In generic code, the question mark (?), called the wildcard, represents an unknown type — java doc file. Wildcard is used to fix the not covarient issue of generic.
. Upper Bounded Wildcards: To declare an upper-bounded wildcard, use the wildcard character (‘?’), followed by the extends keyword, followed by its upper bound. “? extends Number” means it accepts Number class and its child class.
List<Integer> ints =
ints.add(1);
ints.add(2);
List<? extends Number> nums = ints;
nums.add(3.14); //compiler error
With the extends wildcard, values can be gotten but not inserted
. Lower Bounded Wildcards: The type List<? super Integer> consists of objects of any supertype of the Integer class, so objects of type Number and Object are allowed. See the below example.
public class Main{
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(5);
Main.print(list);
List<Number> list1 = new ArrayList<>();
list1.add(5.5);
Main.print(list1);
}
public static void print(List<? super Integer> list){
System.out.println(list);
}
}
However, if we try to assign a type to the return of the get method, we get a compiler error — the compiler has no way of knowing which supertype of Integer is being gotten.
Integer val = list.get(0); //compiler error
Number val = list.get(0); //compiler error
Comparable val= list.get(0); //compiler error
Object val = list.get(0); //OK
. Unbounded Wildcards: The wildcard ?, without the super or extends qualifier, is called the unbounded wildcard. This is called a list of unknown type or “? extends Object”. For example List<?>.
List<?> list = new ArrayList<String>();
list = new ArrayList<Integer>();