At a high level, Generics are parameterized types that allow classes and methods to operate on objects of different types while providing compile-time type safety.
Generics add type safety to the Collections Framework and eliminate the need for explicit type casting. This helps detect errors during compile time itself. Fixing compile-time errors is generally easier than debugging runtime errors. It also helps prevent ClassCastException at runtime.
Let's understand this with an example. First, let's see how to create a List without using Generics:
List list = new ArrayList(); list.add(1); list.add("A"); Integer x = (Integer) list.get(1); //will throw ClassCastException at run time
In the above code, It will add any type of the Object into the list. It won't check the type safety hence there is no compile time error. Only run time it can check for the type safety and will throw ClassCastException.
With Generics, same above code will be written as,
List<Integer> list = new ArrayList<Integer>(); list.add(1); list.add("A"); // It will show a compile time error. Integer x = list.get(0);
The above code shows a compile-time error when attempting to add a String value to the list. This demonstrates type safety provided by Generics at compile time, as it does not allow adding elements of an incorrect type.
In this code, explicit type casting is not required while retrieving values from the list because the compiler already knows the type of elements stored in the collection.
Let us see another advantage of Generics: Type Inference.
Type inference is the ability of the Java compiler to determine the type arguments required for a generic method invocation by analyzing the method call and its context. The compiler examines the types of the arguments passed to the method and, if applicable, the type to which the result is assigned or returned. Based on this information, it determines the most specific type that satisfies all requirements.
Generic methods introduced the concept of type inference, which allows developers to invoke generic methods like normal methods without explicitly specifying the type arguments inside angle brackets (<>).
Generic Classes:-
public class GenericClass<T> { private T t; public void add(T t) { this.t = t; } public T get() { return t; } public static void main(String[] args) { GenericClass<String> stringClass = new GenericClass<String>(); GenericClass<Integer> integerClass = new GenericClass<Integer>(); stringClass.add(new String("Hello World")); integerClass.add(new Integer(1)); System.out.println("Integer Value: " + integerClass.get()); System.out.println("String Value: " + stringClass.get()); } } Output: Integer Value: 1 String Value: Hello World
Wildcards in Generics:-
?) is called a wildcard and represents an unknown type.There are two types of wildcards in Generics:
Bounded Wildcard
Unbounded Wildcard
The bounded wildcard is further divided into two types:
Upper Bounded Wildcard
Lower Bounded Wildcard
Upper Bounded Wildcards
To declare an upper bounded wildcard, use the wildcard character (?) followed by the extends keyword and the upper bound type.
Note that, in this context, extends is used in a general sense to mean either extends (for classes) or implements (for interfaces).
An upper bounded wildcard restricts the unknown type to a specific type or a subtype of that type.
Consider the example below:
public static void sumOfList(List<? extends Number> list) { //some code }
Number type. This method works with lists of Number and its subclasses, such as Integer, Double, and Float.Lower Bounded Wildcards
To declare a lower bounded wildcard, use the wildcard character (?) followed by the super keyword and the lower bound type.
A lower bounded wildcard restricts the unknown type to be a specific type or a superclass of that type.
Consider the example below:
public static void sumOfList(List<? super Integer> list) { //some code }
This method works on a lists of Integer or super type of Integeri.e lists of Number.
Unbounded Generics
An unbounded wildcard is specified using the wildcard character (?), for example, List<?>. It represents a list of an unknown type.
An unbounded wildcard is useful when writing a method that performs only read operations or uses only the common methods available in the Object class, without depending on any implementation-specific methods.
public void display(List<?> list){ Iterator<?> itr = list.iterator(); while (itr.hasNext()) { System.out.println(itr.next()); } } public static int sizeOfList(List<?> list){ return list.size(); }
Type Erasure
Generics were introduced in Java to provide stronger type checking at compile time and to support generic programming.
Type erasure is the process by which the Java compiler removes generic type information during compilation. The compiler replaces type parameters with their bounded types, or with Object if the type parameters are unbounded. As a result, the generated bytecode contains only ordinary classes, interfaces, and methods.
The compiler also inserts type casts wherever required to maintain type safety.
Type erasure ensures that no new classes are created for parameterized types. Therefore, Generics do not introduce any runtime overhead.
Consider the below class:
public class GenericClass<T> { private T t; public void add(T t) { this.t = t; } public T get() { return t; } }
Here T is an unbounded parameter so during type erasure process Java compiler replaces it with the Object. So the code after compilation is as follows,
public class GenericClass { private Object t; public void add(Object t) { this.t = t; } public Object get() { return t; } }
Note : - Standard convention to use Generic parameters are ,
T - Type parameter
E - Elements (using in LinkedList)
K - Key in Map
V - value in Map
N - Number
Thanks Mr Tic....keep visiting blog.
ReplyDelete