Quick Refresh : Java 1.5 : Generics

 

Why Use Generics?

In a nutshell, generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. Much like the more familiar formal parametersused in method declarations, type parameters provide a way for you to re-use the same code with different inputs. The difference is that the inputs to formal parameters are values, while the inputs to type parameters are types. It was introduced in java 1.5.

Benefits of Code that uses generics over non-generic code:

  • Type-safety: We can hold only a single type of objects in generics. It doesn't allow to store other objects. Without Generics, we can store any type of objects.
List list = new ArrayList();    
list.add(10);  
list.add("10");  

With Generics, it is required to specify the type of object we need to store.  
List<Integer> list = new ArrayList<Integer>();    
list.add(10);  
list.add("10");// compile-time error
  • Compile-Time Checking:  A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. The good programming strategy says it is far better to handle the problem at compile time than runtime.

  • Type casting is not required: There is no need to typecast the object.  
    The following code snippet without generics requires casting:
    List list = new ArrayList();
    list.add("hello");
    String s = (String) list.get(0);
    
    When re-written to use generics, the code does not require casting:
    List<String> list = new ArrayList<String>();
    list.add("hello");
    String s = list.get(0);   // no cast
    
  • Enabling programmers to implement generic algorithms: By using generics, programmers can implement generic algorithms that work on collections of different types, can be customized, and are type safe and easier to read.

How Generics works in Java

In the heart of generics is “type safety“. What exactly is type safety? It’s just a guarantee by compiler that if correct Types are used in correct places then there should not be any ClassCastException in runtime. A usecase can be list of Integer i.e. List<Integer>. If you declare a list in java like List<Integer>, then java guarantees that it will detect and report you any attempt to insert any non-integer type into above list.

Another important term in java generics is “type erasure“. It essentially means that all the extra information added using generics into sourcecode will be removed from bytecode generated from it. Inside bytecode, it will be old java syntax which you will get if you don’t use generics at all. This necessarily helps in generating and executing code written prior java 5 when generics were not added in language. Precisely, Generics in Java is nothing but a syntactic sugar to your code for Type Safety and all such type information is erased by Type Erasure feature by compiler.

Generic class or InterfaceA class/interface that can refer to any type is known as a generic class. Here, we are using the T type parameter to create the generic class/interface of specific type.

Let's see a simple example to create and use the generic class.

Creating a generic class:

  1. class MyGen<T>{  
  2. T obj;  
  3. void add(T obj){this.obj=obj;}  
  4. T get(){return obj;}  
  5. }  

Using generic class:

Let's see the code to use the generic class.

  1. class TestGenerics3{  
  2. public static void main(String args[]){  
  3. MyGen<Integer> m=new MyGen<Integer>();  
  4. m.add(2);  
  5. //m.add("vivek");//Compile time error  
  6. System.out.println(m.get());  
  7. }}  

Output

2

Note: If we don’t provide the type at the time of creation, compiler will produce a warning that “GenericsType is a raw type. References to generic type GenericsType<T> should be parameterized”. When we don’t provide type, the type becomes Object and hence it’s allowing both String and Integer objects but we should always try to avoid this because we will have to use type casting while working on raw type that can produce runtime errors.

Tip: We can use @SuppressWarnings("rawtypes") annotation to suppress the compiler warning,

Type Parameters: The type parameters naming conventions are important to learn generics thoroughly. The common type parameters are as follows:

  1. T - Type
  2. E - Element
  3. K - Key
  4. N - Number
  5. V - Value

Generic Method or Constructor: Like the generic class, we can create a generic method or constructor that can accept any type of arguments. Here, the scope of arguments is limited to the method where it is declared. It allows static as well as non-static methods.

  • All generic method declarations have a type parameter section delimited by angle brackets (< and >) that precedes the method's return type.
  • Type parameters can be bounded (bounds are explained later in the article)
  • Generic methods can have different type parameters separated by commas in the method signature
  • Method body for a generic method is just like a normal method
  • We can specify type while calling these methods or we can invoke them like a normal method. Java compiler is smart enough to determine the type of variable to be used, this facility is called as type inference.

Let's see a simple example of java generic method to print array elements. We are using here E to denote the element.


  1. package com.journaldev.generics;

    public class GenericsMethods {

    //Java Generic Method
    public static <T> boolean isEqual(GenericsType<T> g1, GenericsType<T> g2){
    return g1.get().equals(g2.get());
    }

    public static void main(String args[]){
    GenericsType<String> g1 = new GenericsType<>();
    g1.set("Pankaj");

    GenericsType<String> g2 = new GenericsType<>();
    g2.set("Pankaj");

    boolean isEqual = GenericsMethods.<String>isEqual(g1, g2);
    //above statement can be written simply as
    isEqual = GenericsMethods.isEqual(g1, g2);
    //This feature, known as type inference, allows you to invoke a generic method as an ordinary method, without specifying a type between angle brackets.
    //Compiler will infer the type that is needed
    }

Generic Type Arrays

Array in any language have same meaning i.e. an array is a collection of similar type of elements. In java, pushing any incompatible type in an array on runtime will throw ArrayStoreException. It means array preserve their type information in runtime, and generics use type erasure or remove any type information in runtime. Due to above conflict, instantiating a generic array in java is not permitted.

public class GenericArray<T> {
    // this one is fine
    public T[] notYetInstantiatedArray;
  
    // causes compiler error; Cannot create a generic array of T
    public T[] array = new T[5];
}

In the same line as above generic type classes and methods, we can have generic arrays in java. As we know that an array is a collection of similar type of elements and pushing any incompatible type will throw ArrayStoreException in runtime; which is not the case with Collection classes.

Object[] array = new String[10];
array[0] = "lokesh";
array[1] = 10;      //This will throw ArrayStoreException

Above mistake is not very hard to make. It can happen anytime. So it’s better to provide the type information to array also so that error is caught at compile time itself.

Another reason why arrays does not support generics is that arrays are co-variant, which means that an array of supertype references is a supertype of an array of subtype references. That is, Object[] is a supertype of String[] and a string array can be accessed through a reference variable of type Object[].

Object[] objArr = new String[10];  // fine
objArr[0] = new String();

Wildcard in Java Generics

The ? (question mark) symbol represents the wildcard element. It means any type. If we write <? extends Number>, it means any child class of Number, e.g., Integer, Float, and double. Now we can call the method of Number class through any child class object.

We can use a wildcard as a type of a parameter, field, return type, or local variable. However, it is not allowed to use a wildcard as a type argument for a generic method invocation, a generic class instance creation, or a supertype.

  1. import java.util.*;  
  2. abstract class Shape{  
  3. abstract void draw();  
  4. }  
  5. class Rectangle extends Shape{  
  6. void draw(){System.out.println("drawing rectangle");}  
  7. }  
  8. class Circle extends Shape{  
  9. void draw(){System.out.println("drawing circle");}  
  10. }  
  11. class GenericTest{  
  12. //creating a method that accepts only child class of Shape  
  13. public static void drawShapes(List<? extends Shape> lists){  
  14. for(Shape s:lists){  
  15. s.draw();//calling method of Shape class by child class instance  
  16. }  
  17. }  
  18. public static void main(String args[]){  
  19. List<Rectangle> list1=new ArrayList<Rectangle>();  
  20. list1.add(new Rectangle());  
  21.   
  22. List<Circle> list2=new ArrayList<Circle>();  
  23. list2.add(new Circle());  
  24. list2.add(new Circle());  
  25.   
  26. drawShapes(list1);  
  27. drawShapes(list2);  
  28. }}  

Output

drawing rectangle
drawing circle
drawing circle

Upper Bounded Wildcards

The purpose of upper bounded wildcards is to decrease the restrictions on a variable. It restricts the unknown type to be a specific type or a subtype of that type. It is used by declaring wildcard character ("?") followed by the extends (in case of, class) or implements (in case of, interface) keyword, followed by its upper bound.

Syntax

  1. List<? extends Number>  

Here,

? is a wildcard character. extends, is a keyword. Number, is a class present in java.lang package

Suppose, we want to write the method for the list of Number and its subtypes (like Integer, Double). Using List<? extends Number> is suitable for a list of type Number or any of its subclasses whereas List<Number> works with the list of type Number only. So, List<? extends Number> is less restrictive than List<Number>.

Example of Upper Bound Wildcard

In this example, we are using the upper bound wildcards to write the method for List<Integer> and List<Double>.

  1. import java.util.ArrayList;  
  2.   
  3. public class UpperBoundWildcard {       
  4.     private static Double add(ArrayList<? extends Number> num) {       
  5.         double sum=0.0;            
  6.         for(Number n:num)  
  7.         {  
  8.             sum = sum+n.doubleValue();  
  9.         }            
  10.         return sum;  
  11.     }  
  12.   
  13.     public static void main(String[] args) {           
  14.         ArrayList<Integer> l1=new ArrayList<Integer>();  
  15.         l1.add(10);  
  16.         l1.add(20);  
  17.         System.out.println("displaying the sum= "+add(l1));  
  18.           
  19.         ArrayList<Double> l2=new ArrayList<Double>();  
  20.         l2.add(30.0);  
  21.         l2.add(40.0);  
  22.         System.out.println("displaying the sum= "+add(l2))
  23.     }  
  24. }  

Output

displaying the sum= 30.0
displaying the sum= 70.0

Unbounded Wildcards

The unbounded wildcard type represents the list of an unknown type such as List<?>. This approach can be useful in the following scenarios: -

  • When the given method is implemented by using the functionality provided in the Object class.
  • When the generic class contains the methods that don't depend on the type parameter.

Example of Unbounded Wildcards

  1. import java.util.Arrays;  
  2. import java.util.List;  
  3.   
  4. public class UnboundedWildcard { 
  5.     public static void display(List<?> list)  
  6.     {    
  7.         for(Object o:list)  
  8.         {  
  9.             System.out.println(o);  
  10.         }
  11.     } 
  12.     public static void main(String[] args) {  
  13.     List<Integer> l1=Arrays.asList(1,2,3);  
  14.     System.out.println("displaying the Integer values");  
  15.     display(l1);  
  16.     List<String> l2=Arrays.asList("One","Two","Three");  
  17.       System.out.println("displaying the String values");  
  18.         display(l2);  
  19.     } 
  20. }  

Output

displaying the Integer values
1
2
3
displaying the String values
One
Two
Three

Lower Bounded Wildcards

The purpose of lower bounded wildcards is to restrict the unknown type to be a specific type or a supertype of that type. It is used by declaring wildcard character ("?") followed by the super keyword, followed by its lower bound.

Syntax

  1. List<? super Integer>  

Here, ? is a wildcard character. super, is a keyword. Integer, is a wrapper class.

Suppose, we want to write the method for the list of Integer and its supertype (like Number, Object). Using List<? super Integer> is suitable for a list of type Integer or any of its superclasses whereas List<Integer> works with the list of type Integer only. So, List<? super Integer> is less restrictive than List<Integer>.

In this example, we are using the lower bound wildcards to write the method for List<Integer> and List<Number>.

  1. import java.util.Arrays;  
  2. import java.util.List;  
  3.   
  4. public class LowerBoundWildcard {
  5.     public static void addNumbers(List<? super Integer> list) {  
  6.         for(Object n:list)  
  7.         {  
  8.               System.out.println(n);  
  9.         } 
  10.     }  
  11. public static void main(String[] args) {  
  12.       
  13.     List<Integer> l1=Arrays.asList(1,2,3);  
  14.       System.out.println("displaying the Integer values");  
  15.     addNumbers(l1);  
  16.       
  17.     List<Number> l2=Arrays.asList(1.0,2.0,3.0);  
  18.       System.out.println("displaying the Number values");  
  19.     addNumbers(l2);  
  20. }  
  21.   
  22. }  

Output

displaying the Integer values
1
2
3
displaying the Number values
1.0
2.0
3.0

Multiple Bounds:  A type can also have multiple upper bounds as follows:

1
<T extends Number & Comparable>

If one of the types that are extended by T is a class (i.e Number), it must be put first in the list of bounds. Otherwise, it will cause a compile-time error. 

also, i.e <T extends A & B & C>. In this case A can be an interface or class. If A is class then B and C should be interfaces. We can’t have more than one class in multiple bounds.  

Subtyping using Generics Wildcard:

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // OK. List<? extends Integer> is a subtype of List<? extends Number>

Java Generic Classes and Subtyping

We can subtype a generic class or interface by extending or implementing it. The relationship between the type parameters of one class or interface and the type parameters of another are determined by the extends and implements clauses.

For example, ArrayList<E> implements List<E> that extends Collection<E>, so ArrayList<String> is a subtype of List<String> and List<String> is subtype of Collection<String>.

The subtyping relationship is preserved as long as we don’t change the type argument, below shows an example of multiple type parameters.

interface MyList<E,T> extends List<E>{
}

The subtypes of List<String> can be MyList<String,Object>, MyList<String,Integer> and so on. 

What is not allowed to do with Generics?

So far we have learned about a number of things which you can do with generics in java to avoid many ClassCastException instances in your application. We also saw the usage of wildcards as well. Now it’s time to identify some tasks which are not allowed to do in java generics.

a) You can’t have static field of type

You can not define a static generic parameterized member in your class. Any attempt to do so will generate compile time error: Cannot make a static reference to the non-static type T.

public class GenericsExample<T>
{
   private static T member; //This is not allowed
}

b) You can not create an instance of T

Any attempt to create an instance of T will fail with error: Cannot instantiate the type T.

public class GenericsExample<T>
{
   public GenericsExample(){
      new T();
   }
}

c) Generics are not compatible with primitives in declarations

Yes, it’s true. You can’t declare generic expression like List or Map<String, double>. Definitely you can use the wrapper classes in place of primitives and then use primitives when passing the actual values. These value primitives are accepted by using auto-boxing to convert primitives to respective wrapper classes.

final List<int> ids = new ArrayList<>();    //Not allowed
 
final List<Integer> ids = new ArrayList<>(); //Allowed

d) You can’t create Generic exception class

Sometimes, programmer might be in need of passing an instance of generic type along with exception being thrown. This is not possible to do in Java.

// causes compiler error
public class GenericException<T> extends Exception {}

When you try to create such an exception, you will end up with message like this: The generic class GenericException may not subclass java.lang.Throwable.

e)  Java Generics and Inheritance: We know that Java inheritance allows us to assign a variable A to another variable B if A is subclass of B. So we might think that any generic type of A can be assigned to generic type of B, but it’s not the case. Lets see this with a simple program.

package com.journaldev.generics;

public class GenericsInheritance {

public static void main(String[] args) {
String str = "abc";
Object obj = new Object();
obj=str; // works because String is-a Object, inheritance in java

MyClass<String> myClass1 = new MyClass<String>();
MyClass<Object> myClass2 = new MyClass<Object>();
//myClass2=myClass1; // compilation error since MyClass<String> is not a MyClass<Object>
obj = myClass1; // MyClass<T> parent is Object
}

public static class MyClass<T>{}

}

Comments

Popular posts from this blog

Ashtavinayak Temples | 8 Ganpati Temples of Maharashtra | Details Travel Reviews

Ramoji Film City, Hyderabad, India

Pune to Ganpatipule Trip