27 May 2013

Generics in Detail (Java)


If we use Generics in a class that is called as Generic Class. A generic type is a generic class or interface that is parameterized over types.

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.

Generics add stability to your code by making more of your bugs detectable at compile time.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. Heavy usage of Generics can cause disaster. It does not make any difference at runtime but it makes the code not readable and not easy to understand.

Code that uses generics has many benefits over non-generic code:
 Stronger type checks at compile time.
 A Java compiler applies strong type checking to generic code and issues errors if the          code violates type safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find.
 Elimination of casts.

 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.


Why name Generics:-

Generics generalizes the behaviour of the class. By using generic algorithms we can generalize the behaviour of the class or interface.

The following code is without Generics:-

public class Person{

    private String obj;

  public void add(String a){
        obj=a;
  }

  public String get(){
  return obj;
  }
}

In the above code we can store only a “String” object or an object which extends String (String is a final class which cannot be extended) with “add” method and can retrieve “String” object with “get” method. So it’s scope is limited to store a string object.

The following is code with Generics:-

public class Person<T>{

    private T obj;

  public void add(T a){
       obj=a;
  }

  public T get(){
  return obj;
  }
}

By the above code we have generalized the behaviour of the class. Here the scope is not limited to a single type. Here we can pass any type of object and can be retrieved without typecasting the object as compiler adds the typecasting code to the Byte Code. For example the class can be initialized as Person<String> as=new Person<String>() or it can be Person<xxx> as=new Person<xxx>(). It can be initialized with any type(class, Interface).

This is the reason these are called Generics.

How can we make a class Generic class:-

We can make a class Generic by adding type parameters to a class.  Type parameters are different from normal parameters.

Formal or Normal Parameters:-

public class Person{

  private String obj;

  public void add(String a){
       obj=a;
  }

}
In the above code the method “ public void add(String a)” contains a single parameter that is “a”. The type of the parameter “a” is String. When this method is invoked from a different class we pass an argument to the parameter “a”. The argument is an value (Object). We pass an object of type String as an argument to the parameter “a”. Once the parameter is defined that can used inside the method. Parameter is nothing but a variable declared in the method definition. Parameters have to be defined first so that they can be referred inside the method body.  The parameters will be defined inside the parentheses just after the constructor or method name.

Type Parameters:-

Type parameters are the variables defined in angle brackets. The variable name can be anything(t, ta, type, xxx). It is like any normal variable in java but by convention to differentiate between normal parameter and type parameter a single uppercase letter is used as the parameter and it does not have any data type. Normal parameter have a data type when defined but type parameter does not have a data type when defined. for normal parameters we send objects as arguments but for type parameters we pass data types(name of the class) as arguments. Type parameters are defined inside the angle brackets just after the class name or just before the methods return type.
any number of variables can be defined separated by comma. As we are passing types as arguments to this parameters they are called type parameters. Once the type parameters are defined they can be referenced elsewhere in the class.

Example:-

public class Person<T>{

    private T obj;

  public void add(T a){
  obj=a;
  }
}

In the above class T is defined after the class name in angle brackets and has been referenced throughout the class.

How to understand Generics:-

Generic class:-
public class Person<T>{

    private T obj;

  public void add(T a){
  obj=a;
  }
           
             public T get(){
  return obj;
  }

}

when we pass arguments to the type parameters it is to say to the compiler how the objects should be casted (to what data type they should be casted) rather than what data type objects the type parameter references can hold.

for example when the person class is created “Person<String> per=new Person<String>()” . This is to say to the compiler that all the objects passed to the references of type parameters should be casted to String but the type parameter references can hold any object of String class or any class object that extends String (Note :- String is a final class and cannot be extended) but when we retrieve the object from the type parameter reference variable it will only be type casted to string .

The following declarations will be incorrect:-

String cannot be extended but think that StringEx extends String. Now StringEx is the subclass of String. So “Person<String> per=new Person<StringEx>()” is incorrect. why because as i said when we pass the arguments to the type parameters it is just to say to the compiler to what type the object that is passed to the type parameter reference variable has to casted when necessary.  so when compiler encounters the above statement it will get confused whether the object that is passed to type parameter reference variable has to be casted to “String” or “StringEx” as every “StringEx” object can be “String” but Every “String” object cannot be “StringEx”.  So the correct way to define the above statement is “Person<String> per=new Person<String>()” or “Person<StringEx> per = new Person<StringEx>()”.

But when we are passing data to the type parameter reference variable “a” in “add” method of person class it can be a “String” or “StringEx” object but finally when we obtain the stored object using “get” method in Person class the return type will be “String” no matter whether it is a subclass object of String class.

Now in java 1.7 as they found it is meaningless to pass the same data type argument on both side of the assignment operator they have introduced “Diamond”. With this the Person class object can be created as follows.
                    Person<String> per= new Person<>()
The above code is equivalent to
                    Person<String> per=new Person<String>()

Raw Type:-

When generic class object is created without passing arguments to type parameters then they are called raw types.

For example if the Person class object is created as “Person per=new Person()” this is called raw type as we have ignored type parameters without passing arguments to it. You also get a warning if you use a raw type to invoke generic methods defined in the corresponding generic type.


Generic Methods:-

Generic methods are methods that introduce their own type parameters. This is similar to declaring a generic type, but the type parameter's scope is limited to the method where it is declared. Static and non-static generic methods are allowed, as well as generic class constructors.

The syntax for a generic method includes a type parameter, inside angle brackets, and appears before the method's return type. Despite of the class having type parameters defined the methods as well also can have its own type parameters defined and can be used within the method. The scope of these defined parameter types only to that particular method.

Example:-

public class Util {
    // Generic static method
    public static <W> W compare(W as) {
        return as;
    }
}

In the above class the static method is defined with generics.  The <W> just before the return type says that a generic <W> is defined in the method “compare” and can be used in the “compare” method. So if the compare method is invoked with String object then the return type also will be the String object as the return type we specified is “W”. So after defined it has been referenced in two places. one is as parameter type and the second one is as return type.

Bounded Type Parameters:-

There may be times when you want to restrict the types that can be used as type arguments in a parameterized type. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses. This is what bounded type parameters are for.

To declare a bounded type parameter, list the type parameter's name, followed by the extends keyword, followed by its upper bound, which in this example is Number. Note that, in this context, extends is used in a general sense to mean either "extends" (as in classes) or "implements" (as in interfaces).

Examples:-

public <U extends Number> void inspect(U u){
        System.out.println("T: " + t.getClass().getName());
        System.out.println("U: " + u.getClass().getName());
    }


In the above example the method uses a bounded type parameter. In the type parameter definition it is defined as “<U extends Number>” that means the type parameter variable “u” can accept only objects that extends “Number”.

public class NaturalNumber<T extends Integer> {

    private T n;

    public NaturalNumber(T n)  { this.n = n; }

    public boolean isEven() {
        return n.intValue() % 2 == 0;
    }
}

In the above example The class “NaturalNumber” can accept only type arguments which extends Integer.

Multiple Bounds:-

The preceding example illustrates the use of a type parameter with a single bound, but a type parameter can have multiple bounds:
<T extends B1 & B2 & B3>
In this example The data type that is passed to type parameter “T” should implement B1 and B2 and B3. If one of the bounds is a class, it must be specified first.

 Generic Constructors:-

 class MyClass<X> {
  <T> MyClass(T t) {
    // ...
  }
}

Invocation of the above class could be “new MyClass<Integer>("xxx")”.

Wild Cards:-

In generic code, the question mark (?), called the wildcard, represents an unknown type. The wildcard can be used in a variety of situations: as the type of a parameter, field, or local variable; sometimes as a return type (though it is better programming practice to be more specific). The wildcard is never used as a type argument for a generic method invocation, a generic class instance creation, or a supertype.

To declare an upper-bounded wildcard, use the wildcard character ('?'), followed by the extends keyword, followed by its upper bound. Note that, in this context, extendsis used in a general sense to mean either "extends" (as in classes) or "implements" (as in interfaces).

Example:-

public static void process(List<? extends Foo> list) {
    for (Foo elem : list) {
        // ...
    }
}

The upper bounded wildcard, <? extends Foo>, where Foo is any type, matches Foo and any subtype of Foo. The process method can access the list elements as typeFoo.

Unbounded wild cards:-

The unbounded wildcard type is specified using the wildcard character (?), for example, List<?>. This is called a list of unknown type. There are two scenarios where an unbounded wildcard is a useful approach:

If you are writing a method that can be implemented using functionality provided in the Object class.
When the code is using methods in the generic class that don't depend on the type parameter. For example, List.size or List.clear. In fact, Class<?> is so often used because most of the methods in Class<T> do not depend on T.


Example:-

public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

In the above example the parameter “list” can accept any thing such as ArrayList that contains Strings or Integers or any objects.

The code “List<?> list=new ArrayList<String>()” is valid statement but we cannot add any objects to arraylist. If we try to add an object to arraylist compiler error will be thrown because compiler cannot infer what kind of object is safe to add as <?> denotes unknown type.

Lower Bounded Wildcards:-

A lower bounded wildcard restricts the unknown type to be a specific type or a super type of that type.  A lower bounded wildcard is expressed using the wildcard character ('?'), following by the super keyword, followed by its lower bound: <? super A>

Example:-

public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}
In the above method the parameter “list” can accept eighter an Integer object or the object of any of its super class. so passing an ArrayList which holds Integers or ArrayList that holds Number objects or ArrayList that holds objects of Object class can be passed to the parameter “list”.

Wildcard capture and Helper Methods:-

In some cases, the compiler infers the type of a wildcard. For example, a list may be defined as List<?> but, when evaluating an expression, the compiler infers a particular type from the code. This scenario is known as wildcard capture.

The WildcardError example produces a capture error when compiled:

import java.util.List;

public class WildcardError {

    void foo(List<?> i) {
        i.set(0, i.get(0));
    }
}

The above code produces compiler error when an object is being inserted to list as compiler cannot infer what object it can add safely to the list as <?> says unknown.

So there is a work around for this kind of problems. we can write additional methods which are called “Helper Methods” to solve this kind of problems.

public class WildcardFixed {

    void foo(List<?> i) {
        fooHelper(i);
    }

    // Helper method created so that the wildcard can be captured
    // through type inference.
    private <T> void fooHelper(List<T> l) {
        l.set(0, l.get(0));
    }
}

Type Erasure:-

Generics were introduced to the Java language to provide tighter type checks at compile time and to support generic programming. Generics will be removed by the compiler when converting source to bytecode. However the class files contain byte code and also some additional information such as Generics which are useful for the compiler and VM for further processing.



To implement generics, the Java compiler applies type erasure to:

Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
Insert type casts if necessary to preserve type safety.
Generate bridge methods to preserve polymorphism in extended generic types.
Type erasure ensures that no new classes are created for parameterized types; consequently, generics incur no runtime overhead.

Generic class in source file:-

public class Person<T>{
 
       public T add(T a){
               System.out.println("happy");
               return a;
       }
}

The same above class in Byte Code:-
public class Person{
 
       public Object add(Object a){
               System.out.println("happy");
               return a;
       }
}

Restrictions on Generics:-

 cannot instanciate Geniric types with primitive types.
               Person<int> per = new Person<int>() // throws compiler error.

       2. cannot create instances of type parameters.
               public static <E> void append(List<E> list) {
                        E elem = new E();  // compile-time error
                        list.add(elem);
             }
     
 3. cannot declare static fields whose types are type parameters.
                public class MobileDevice<T> {
                            private static T os;  // compiler error.
                }  
 4. cannot use casts or instance of with parameterised types. Because the Java compiler erases all type parameters in generic code, you cannot verify which parameterized type for a generic type is being used at runtime:
           
                 public static <E> void rtti(List<E> list) {
                         if (list instanceof ArrayList<Integer>) {  // compile-time error
                        }
                 }

Typically, you cannot cast to a parameterized type unless it is parameterized by unbounded wildcards. For example:
                  List<Integer> li = new ArrayList<>();
                  List<Number>  ln = (List<Number>) li;  // compile-time error

5. cannot create arrays of parameterised type.
             Object[] stringLists = new List<String>[];  // compiler error, but pretend it's allowed
             stringLists[0] = new ArrayList<String>();   // OK
              stringLists[1] = new ArrayList<Integer>();  // An ArrayStoreException should be thrown,
                                                                                // but the runtime can't detect it.
6. cannot create catch or throw objects of parameterised types.
A generic class cannot extend the Throwable class directly or indirectly. For example, the following classes will not compile:
                 // Extends Throwable indirectly
                 class MathException<T> extends Exception { /* ... */ }    // compile-time error

                 // Extends Throwable directly
                class QueueFullException<T> extends Throwable { /* ... */ // compile-time error

                            A method cannot catch an instance of a type parameter:

                          public static <T extends Exception, J> void execute(List<J> jobs) {
                                try {
                                        for (J job : jobs){}
         
                                   } catch (T e) {   // compile-time error
                                      }
                       }
                        You can, however, use a type parameter in a throws clause:
                       class Parser<T extends Exception> {
                                public void parse(File file) throws T {     // OK
                                }
                        }
  7. Cannot Overload a Method Where the Formal Parameter Types of Each Overload Erase to the Same Raw Type.

 public class Example {
      public void print(Set<String> strSet) { }
      public void print(Set<Integer> intSet) { }
    }
The overloads would all share the same classfile representation and will generate a compile-time error.
Post a Comment