☰ See All Chapters |
Generics in Java- How Generics Improve Type Safety
Assume we want to create a class that will work with various kinds of data. This can be done using generics and also by references of type object. Because Object is the superclass of all other classes, an Object reference can refer to any type object. Thus, in pre-generics code, generalized classes, interfaces, and methods used Object references to operate on various types of objects. First let us learn how to create such a class (class that will work with various kinds of data) Using Object references and problems found in this case.
Value 88 and "Generics Test” are Constructor arguments. Generic Classes don't infer types from constructor arguments.
Syntax:
Java 1.6 Syntax: Gen<Integer> iOb = new Gen<Integer>(88);
Java 1.7 Syntax: Gen<Integer> iOb = new Gen(88);
Generics is introduced in JDK1.5
Classes, Class methods and Interfaces can be generic.
Generics work only with objects, not with primitive type. If primitive type is used then it will be converted to wrapper classes (wrapper classes will be studied later)
Gen<int> iOb = new Gen<int>(53); // Error, can't use primitive type
Value 53 is auto boxed and object appearance is obtained.
Gen<int> iOb = new Gen<Integer>(new Integer(88)); <==> Gen<int> iOb = new Gen<int>(53);
Java professional rarely write generics, but extensively uses generics from libraries.
We can discard the generic support given by generic classes. Which we do for adding dissimilar data into some collection classes (those which allow dissimilar data).
class Gen<T> { T ob;
Gen(T o) { ob = o; }
T getob() { return ob; }
void showType() { System.out.println("Type of T is " + ob.getClass().getName()); } }
class GenDemo { public static void main(String args[]) { Gen iOb = new Gen(88); // neglecting generic support iOb.showType(); int v = (Integer) iOb.getob(); // type casting is required System.out.println("value: " + v); } } |
Type parameter Naming Convention
Usually type parameter names are single, uppercase letters to make it easily distinguishable from java variables. The most commonly used type parameter names are:
E – Element (used extensively by the Java Collections Framework, for example ArrayList, Set etc.)
K – Key (Used in Map)
N – Number
T – Type
V – Value (Used in Map)
S,U,V etc. – 2nd, 3rd, 4th types
A Generic Class with Two Type Parameters
class TwoGen<T, V> { T ob1; V ob2; TwoGen(T o1, V o2) { ob1 = o1; ob2 = o2; } void showTypes() { System.out.println("Type of T is " + ob1.getClass().getName()); System.out.println("Type of V is " + ob2.getClass().getName()); } T getob1() { return ob1; } V getob2() { return ob2; } } class GenDemo { public static void main(String args[]) { TwoGen<Integer, String> tgObj = new TwoGen<Integer, String>(88, "Generics"); tgObj.showTypes(); int v = tgObj.getob1(); System.out.println("value: " + v); String str = tgObj.getob2(); System.out.println("value: " + str); } } |
Ouput:
Type of T is java.lang.Integer Type of V is java.lang.String value: 88 value: Generics |
The General Form of a Generic Class
As above program we can have more than one type parameter. The generics syntax shown in the preceding examples can be generalized. Here is the syntax for declaring a generic class:
class className<type-param-list> { // ...
Here is the syntax for declaring a reference to a generic class:
className<type-arg-list> var-name = new className<type-arg-list>(cons-arg-list);
The catch mechanism only works with non-generic types.
Bounded Types
Type Parameter | Type Arguements | Upper Bound |
class Gen<T extends A> | A and sub classes of A (A,B,C,D) | A |
class Gen<T extends B> | B and sub classes of B (B,C,D) | B |
class Gen<T extends C> | C and sub classes of C (C Only) | C |
Type arguments can be direct/indirect subclasses of Upper Bound.
To set Upper Bound Syntax is
<Type Parameter extends superclass>
Bound can be
Class (only one class at a time)
Interface, can be one or more.
Combination of class and interface.(Only one class at a time)
When a bound includes an interface type, only type arguments that implement that interface are legal. When specifying a bound that has a class and an interface, or multiple interfaces, use the & operator to connect them. For example:
class Gen<T extends MyClass & MyInterface> { // ...
Here, T is bounded by a class called MyClass and an interface called MyInterface. Thus, any type argument passed to T must be a subclass of MyClass and implement MyInterface.
Type Parameter | Type Arguements | Upper Bound |
class Gen<T extends A>{ T val; Gen(T val){ this.val=val; } } | Gen<A> obj1 =new Gen<>(new A()); Gen<B> obj2 =new Gen<>(new B()); Gen<C> obj3 =new Gen<>(new C());
| A |
class Gen<T extends B>{ T val; Gen(T val){ this.val=val; } } | Gen<B> obj2 =new Gen<>(new B()); Gen<C> obj3 =new Gen<>(new C());
| B |
class Gen<T extends C>{ T val; Gen(T val){ this.val=val; } } | Gen<C> obj3 =new Gen<>(new C());
| C |
Advantage of using Bounded Types
Calculate sum of elements of a numeric array. Array can be of any numeric data type like byte, short, int, long, float, double. Calculate the sum of double version of elements.
In Stats, the average( ) method attempts to obtain the double version of each number in the nums array by calling doubleValue( ). Because all numeric classes, such as Integer and Double, are subclasses of Number, and Number defines the doubleValue( ) method, this method is available to all numeric wrapper classes. The trouble is that the compiler has no way to know that you are intending to create Stats objects using only numeric types. Thus, when we try to compile Stats, an error is reported that indicates that the doubleValue( ) method is unknown. To solve this problem, we need some way to tell the compiler that we intend to pass only numeric types to T. Furthermore, we need some way to ensure that only numeric types are actually passed. To handle such situations, Java provides bounded types.
What if Generic classes are used as parameters in methods?
Considering the following code, below table is an example for when Generic classes are used as parameters in methods.
class A{} class B extends A{} class C extends B{} class Gen<T>{ } class D { void methD(Gen<?> ref){ System.out.println("HellO"); } } public class BoundedTypes { public static void main(String[] args) { Gen<A> obj1 =new Gen<>(); Gen<B> obj2 =new Gen<>(); Gen<C> obj3 =new Gen<>(); Gen<Integer> obj4 =new Gen<>(); D d= new D(); d.methD(obj1); } } |
Type Parameter | Type Arguements | method parameter | method Arguements |
Gen<T > | Any Objects Gen<A> obj1 =new Gen<>(new A()); Gen<B> obj2 =new Gen<>(new B()); Gen<C> obj3 =new Gen<>(new C()); Gen<Integer> obj4 =new Gen<>(); | void methD(Gen<?> ref)
| d.methD(obj1); d.methD(obj2); d.methD(obj3); d.methD(obj4);
|
void methD(Gen<A> ref) | d.methD(obj1); | ||
void methD(Gen<B> ref) | d.methD(obj2); | ||
void methD(Gen<Integer> ref) | d.methD(obj4); | ||
void methD(Gen< ? extends A> ref) | obj1,obj2,obj3 | ||
void methD(Gen<? extends B> ref) | obj2,obj3 | ||
void methD(Gen<? extends c> ref) | obj3 | ||
void methD(Gen< ? super A> ref) | obj1 | ||
void methD(Gen< ? super B> ref) | obj1,obj2 | ||
void methD(Gen< ? super C> ref) | obj1,obj2,obj3 | ||
Gen<T extends A> | A and sub classes of A (A,B,C) Gen<A> obj1 =new Gen<>(new A()); Gen<B> obj2 =new Gen<>(new B()); Gen<C> obj3 =new Gen<>(new C());
| void methD(Gen<?> ref | obj1,obj2,obj3 |
void methD(Gen<A> ref) | d.methD(obj1); | ||
void methD(Gen<B> ref) | d.methD(obj2); | ||
void methD(Gen<C> ref) | d.methD(obj3); | ||
void methD(Gen< ? extends A> ref) | obj1,obj2,obj3 | ||
void methD(Gen<? extends B> ref) | obj2,obj3 | ||
void methD(Gen<? extends c> ref) | obj3 | ||
void methD(Gen< ? super A> ref) | obj1 | ||
void methD(Gen< ? super B> ref) | obj1,obj2 | ||
void methD(Gen< ? super C> ref) | obj1,obj2,obj3 | ||
Gen<T extends B> | B and sub classes of B (B,C) Gen<B> obj2 =new Gen<>(new B()); Gen<C> obj3 =new Gen<>(new C());
| void methD(Gen<?> ref | obj2,obj3 |
void methD(Gen<B> ref) | d.methD(obj2); | ||
void methD(Gen<C> ref) | d.methD(obj3); | ||
void methD(Gen<? extends B> ref) | obj2,obj3 | ||
void methD(Gen<? extends C> ref) | obj3 | ||
void methD(Gen< ? super B> ref) | obj2 | ||
void methD(Gen< ? super C> ref) | obj2,obj3 | ||
Gen<T extends B> | C and sub classes of C(Only C) Gen<C> obj3 =new Gen<>(new C()); | void methD(Gen<?> ref | obj3 |
void methD(Gen<? extends C> ref) | obj3 | ||
void methD(Gen< ? super C> ref) | obj3 |
Generic Method
Like generic class, we can create generic method that can accept any type of argument.
Let’s see a simple example of java generic method to print array elements. We are using here E to denote the element.
public class GenericMethodDemo{ public static < E > void printArray(E[] elements) { for ( E element : elements){ System.out.print(element+" " ); } System.out.println(); } public static void main( String args[] ) { Integer[] intArray = { 10, 20, 30, 40, 50 }; Character[] charArray = {'A','d','i','T','e','m','p' }; System.out.println( "Printing Integer Array" ); printArray( intArray ); System.out.println( "Printing Character Array" ); printArray( charArray ); } } |
Output:
Printing Integer Array 10 20 30 40 50 Printing Character Array A d i T e m p |
Erasure
An important constraint that governed the way that generics were added to Java was the need for compatibility with previous versions of Java. Simply put, generic code had to be compatible with preexisting, non-generic code. Thus, any changes to the syntax of the Java language, or to the JVM, had to avoid breaking older code. The way Java implements generics while satisfying this constraint is through the use of erasure.
In general, here is how erasure works. When your Java code is compiled, all generic type information is removed (erased). This means replacing type parameters with their bound type, which is Object if no explicit bound is specified, and then applying the appropriate casts (as determined by the type arguments) to maintain type compatibility with the types specified by the type arguments. The compiler also enforces this type compatibility. This approach to generics means that no type parameters exist at run time. They are simply a source-code mechanism.
This mechanism of erasing all details about generic after compilation is known as type erasing.
All Chapters