Showing posts with label Generics. Show all posts
Showing posts with label Generics. Show all posts

Limitations of generics

In this section we will try to find out some of the limitations in use of generics.
  • By this time you must have understood the possible applicability of generics. Generics are used mainly when one object contains other object. It may be a simple class or a collection. You can add restriction on what can be added to other class. It is not possible to add such a restriction in other ways of reusing e.g. inheritance.
  • Generics work on objects and not on primitive data types.
  • When the instances of certain declared classes are injected using Dependency Injection (say using Spring), then you can have partial usage of generics i.e. only the declarations will be made using generics, but actual instantiation depends on if the injection supports generics.
  • Generics reduce the polymorphic nature of code. At compile time only, we finalize the concrete data types to be dealt with, this leaves little opportunity for runtime alteration.

Example comparing the legacy with generic styles

Legacy non-generic example   
// Typical Collections usage before Java 5
List greetings = new ArrayList();
greetings.add("We come in peace.");
greetings.add("Take me to your leader.");
greetings.add("Resistance is futile.");

Iterator it = greetings.iterator();
while (it.hasNext()) {
    String aGreeting = (String)it.next();
    attemptCommunication(aGreeting);
}

Same example using generics
    // Same example using generics.
List<String> greetings = new ArrayList<String>();
greetings.add("We come in peace.");
greetings.add("Take me to your leader.");
greetings.add("Resistance is futile.");

Iterator<String> it = greetings.iterator();
while (it.hasNext()) {
    String aGreeting = it.next();  // No downcast.
    attemptCommunication(aGreeting);
}

Inheritance and Overriding

Classes can extend generic classes, and provide values for type parameters or add new type parameters by in doing so. For example, all the following are legal:
 class MyStringList extends ArrayList<String> { ... }
 class A<X, Y, Z> { ... }
 class B<M,N> extends A<N, String, Integer> { ... }
The addition of generics does, however, change the rules of method overriding slightly. Note in the first example above (MyStringList) the effective signatures of the 'get' method in ArrayList and our subclass:
ArrayList:
 public Object get(int i);
MyStringList:
 public String get(int i);
Through inheritance, the return type has changed. Prior to Java 1.5, this would not have been legal; however, with the addition of parameterized types, the rule has change from "the return types must be identical" to "the return type of the method must be a subtype of all the methods it overrides." As such, the following, which would not compile in a 1.4 environment, is perfectly acceptable in 1.5:
  public class Parent {
    public Number getX() { ... }
  }
  public class Child extends Parent {
    public Integer getX() { ... }
  }

Exception Handling in Generics

Type parameters may be used in the throws clause of a method declaration, but (to preserve JVM compatibility) not in a catch clause. shows an example.

 Generic exception handling.

 public interface MyAction <E extends Exception> {

  public void doWork() throws E;

 }

 public class MyFileAction implements MyAction<FileNotFoundException> {

 public void doWork() throws FileNotFoundException {

   new File("/foo/bar.txt");

  // do something here...

 }

 }

 // client code

 MyFileAction fileAction = new MyFileAction();

  try {

   fileAction.doWork();

  }

  catch (FileNotFoundException e) {

   e.printStackTrace();

  } 

Type Erasure of Generics

When a generic type is instantiated, the compiler translates those types by a technique called type erasure — a process where the compiler removes all information related to type parameters and type arguments within a class or method. Type erasure enables Java applications that use generics to maintain binary compatibility with Java libraries and applications that were created before generics.
For instance, Box<String> is translated to type Box, which is called the raw type — a raw type is a generic class or interface name without any type arguments. This means that you can't find out what type of Object a generic class is using at runtime. The following operations are not possible:
public class MyClass<E> {
    public static void myMethod(Object item) {
        // Compiler error
        if (item instanceof E) {
            ...
        }
        // Compiler error
        E item2 = new E();
        // Compiler error
        E[] iArray = new E[10];
        // Unchecked cast warning
        E obj = (E)new Object();
    }
}
The operations shown in bold are meaningless at runtime because the compiler removes all information about the actual type argument (represented by the type parameter E) at compile time.

Wrong Use of Generics

When we define generics, we use Object as a data type where we are not sure about the actual type.
Map<String, Object> data = new HashMap<String, Object>();
We are not doing any value add by defining the generic as Object (You must be remembering that Object class is the Super parent class).

Subtyping of Generics

As you already know, it's possible to assign an object of one type to an object of another type provided that the types are compatible. For example, you can assign an Integer to an Object, since Object is one of Integer's supertypes:
Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger; // OK
In object-oriented terminology, this is called an "is a" relationship. Since an Integer is a kind of Object, the assignment is allowed. But Integer is also a kind of Number, so the following code is valid as well:
public void someMethod(Number n){
    // method body omitted
}
someMethod(new Integer(10));  // OK
someMethod(new Double(10.1)); // OK
The same is also true with generics. You can perform a generic type invocation, passing Number as its type argument, and any subsequent invocation of add will be allowed if the argument is compatible with Number:
Box<Number> box = new Box<Number>();
box.add(new Integer(10));  // OK
box.add(new Double(10.1)); // OK

What is Wildcard in Generics?

When no specific knowledge of the value of a type parameter is needed, it can be replaced by a wildcard. The wildcard alone, represented by a question mark, takes the value of the default bound of the corresponding formal type parameter, which is Object in following example:
 

void printElements(List<?> myList) { ... }


A wildcard might include an upper bound, which states that the value of the type parameter must be equal to, or a descendent of the bound, as in the following:


 void addAll(Collection<? extends E>) { ... {


A wildcard may alternately specify a lower bound, as in the following:


 void addAll(Collection<? super E>) { ... {


example of upperbound wildcard


It is possible to set the upper bound of the wildcard like this:


List<? extends Vehicle> vehicles = new ArrayList<? extends Vehicle>();   


In this example I have specified the upper bound to be the class Vehicle. I can now define the processElements() method like this:


public void processElements(List<? extends Vehicle> elements){
   for(Vehicle o : elements){
      System.out.println(o);
   }
}


As you can see it is now safe to cast each element in the list to a Vehicle, as it is done by the new for loop inside the method.
Furthermore, it is now safe to call the method using a List<Car> instance, provided that Car extends Vehicle. Here is an example:


List<Car> elements = new ArrayList<Car>
// ... add Car elements to the list.
processElements(elements);


But, even when using a wildcard with an upper bound it is not safe to write to the List. After all, a Car is always a Vehicle, but a Vehicle is not always a Car.

By using wildcard, Well, we got:
1) A way to specify that a collection can contain any type.
2) A way to specify that a collection can contain any subtype of X.
3) ... and this at the price of not being able to write to such a collection.

Explain Bounded type parameters of Generics

A declaration of type parameters for a class or an interface is allowed to place restrictions on the relations between the types involved. These restrictions are of the form of subtype (supertype) relations. For example
public class Foo<S,T extends S> {
    ...
}
declares two type parameters S and T for class Foo, but requires in addition that, in any instantiation, T must be a subtype of S. (It can be expected that this relation between the two types will be exploited in the code defining the class Foo, otherwise it would be pointless.)
Here is a more concrete example in which one of the types is constant.
public class SortedList<E extends Comparable> {
    public void insert(E item);
    ...
}
The elements of a sorted list (or priority queue or binary search tree etc.) must be comparable with respect to order, since this will be assumed in the insertion routine. The type declaration ensures this by requiring that elements of type E implement the Comparable interface:
public interface Comparable {
    public int compareTo(Object o);
}
It is instructive to compare this with the earlier approach to such classes. Previously a generic sorted list might be defined by
public class SortedList {
    public void insert(Comparable item);
    ...
}
This will ensure that any object inserted into the list implements a compareTo method. But it does not ensure that it is the same relation. For example:
SortedList list = new SortedList();
list.insert(new Integer(0));
list.insert(new Float(0.0));
will succeed at compile-time but lead to a ClassCastException at run-time, when an attempt is made to compare the order of an Integer and a Float.
If instead, in Java 5, we use the definition
public class SortedList<E extends Comparable> {
    public void insert(E item);
    ...
}
then the compiler will force the programmer to decide between
SortedList<Float> list = new SortedList<Float>();
and
SortedList<Integer> list = new SortedList<Integer>();
so that one or other of the lines
list.insert(new Integer(0));
list.insert(new Float(0.0));
will already fail at compile-time.

The Diamond - Generics

 In Java SE 7 and later, you can replace the type arguments required to invoke the constructor of a generic class with an empty set of type parameters (<>) as long as the compiler can determine, or infer, the type arguments from the context. This pair of angle brackets, <>, is informally called the diamond. For example, you can create an instance of Box<Integer> with the following statement:
Box<Integer> integerBox = new Box<>(); 
     
Generic Methods: 

You can write a single generic method declaration that can be called with arguments of different types. Based on the types of the arguments passed to the generic method, the compiler handles each method call appropriately. Following are the rules to define Generic Methods:
- All generic method declarations have a type parameter section delimited by angle brackets (< and >) that precedes the method's return type ( < E > in the next example).
- Each type parameter section contains one or more type parameters separated by commas. A type parameter, also known as a type variable, is an identifier that specifies a generic type name.
- The type parameters can be used to declare the return type and act as placeholders for the types of the arguments passed to the generic method, which are known as actual type arguments.
- A generic method's body is declared like that of any other method. Note that type parameters can represent only reference types not primitive types (like int, double and char).
   
Example:
 
Following example illustrate how we can print array of different type using a single Generic method:
public class GenericMethod
{
   // generic method printArray                        
   public static < E > void printArray( E[] inputArray )
   {
      // Display array elements             
         for ( E element : inputArray ){       
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }

    public static void main( String args[] )
    {
        // Create arrays of Integer, Double and Character
        Integer[] integerArray = { 1, 2, 3, 4, 5 };
        Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
        Character[] characterArray = { 'H', 'E', 'L', 'L', 'O' };

        System.out.println( "Array integerArray contains:" );
        printArray( integerArray ); // pass an Integer array

        System.out.println( "\nArray doubleArray contains:" );
        printArray( doubleArray ); // pass a Double array

        System.out.println( "\nArray characterArray contains:" );
        printArray( characterArray ); // pass a Character array
    }
}
This would produce following result:
Array integerArray contains:
1 2 3 4 5 6

Array doubleArray contains:
1.1 2.2 3.3 4.4

Array characterArray contains:
H E L L O

What is Generics?

Generics are a built-in language feature that will make your software more reliable .The feature of Generics in Java allows Applications to create classes and objects that can operate on any defined types. Programmers can now make use of the Generics feature for a much better code. There is no need for un-necessary casting when dealing with Objects in a Collection.

Why we use generics?
Generics add stability to your code by making more of your bugs detectable at compile time.
Let's begin by designing a nongeneric class.

public class NonGenerics {

        public static void main(String[] args) {
                Map data = new HashMap();
                data.put("Key1", new Integer(100));
                System.out.println((Integer)data.get("Key1"));
        }
}
Take a closer look at the last line involving casting of object pulled out of the HashMap. There is no restriction from Map or HashMap on what type of object we add to this collection. The only requirement is – it should be an object i.e. we cannot add primitive to the collection unless it is wrapped in its respective object. Can you visualize problem with above approach?
User of the data from collection is expecting that the data should be of type Integer, but the creator of data is not having any such restriction. If the data added is of type other than Integer then there will be a ClassCastException. e.g. instead of Integer you add String to the collection, and end up in ClassCastException when you try to call any method of Integer. When do you come to know about this problem? It is at runtime, which is not correct. The solution is – use of generics. Let us change above code to use generics.
import java.util.HashMap;
import java.util.Map;

public class UseGenerics {

        public static void main(String[] args) {
                Map<String, Integer> data = new HashMap<String, Integer>();
                data.put("Key1", new Integer(100));
                System.out.println(data.get("Key1"));
        }
}
here we are restricting the kind of objects we can add to the collection .we fix that key should be String type only and value should be Integer type...If at all you try to add any other type of data then there is compile time error. This means that the compiled code is generated using the generics only.

Share

Twitter Delicious Facebook Digg Stumbleupon Favorites More