Saturday, April 7, 2012

Generics in Java

Dear reader,

After a long duration I got time to discuss Generics and WildCard characters in Java. 
Generics are quite common now a days and is used heavily in applications. But generally we use 
Typed collections like List<String>, we say as List of String types. However here we will explore some 
more things.

Generics are basically for type-checking/type-safety, which is ERASED once compilation is done. I mean at 
run-time there is no generic concepts in JVM. So if you don't put your objects (type of objects) properly in 
collection or array, you may end up with ClassCastException or ArrayStoreException at run-time while fetching 
objects. 

This article covers:
 1) Normal program on Generics and how to use that. Comments are provided in program itself.
 2) Output of all those programs at the end of each program.
 3) "? extends T" type Generics.
 4) "? super T" type Generics.
 5) Implementation of "? extends" and "? super" in methods.
 6) Implementation of "T" in interfaces.

NOTE: Generics are not co-variant (please read further for co-variant) but Arrays are. 
      While you might find it helpful to think of collections as being an abstraction of arrays, they have some 
      special properties that collections do not. Arrays in the Java language are covariant -- which means that if 
      Integer extends Number (which it does), then not only is an Integer also a Number, but an Integer[] is also a 
      Number[], and you are free to pass or assign an Integer[] where a Number[] is called for. (More formally, 
      if Number is a supertype of Integer, then Number[] is a supertype of Integer[].) You might think the same is 
      true of generic types as well -- that List<Number> is a supertype of List<Integer>, and that you can pass a 
      List<Integer> where a List<Number> is expected. Unfortunately, it doesn't work that way. See below ex:
     
    Number[] n=new Number[3];
    Integer[] m=new Integer[3];
    n=m;  //Allowed
    m=n;  //Wrong. Reference of Super class to sub-class not allowed. Won't compile.
        
    List<Integer> li = new ArrayList<Integer>();
    List<Number> ln = new ArrayList<Number>();
    List<Number> ln2 = li;  //Illegal, Won't compile.
    ln.add(new Float(3.1415)); //Adding number is Ok, as Float is a Number sub type.
     
Now lets discuss Generics in detail. I am giving proper names for classes for representing super and 
sub-classes like:
1. Fruit super class
2. --Mango extends Fruit
3. --Papaya extends Fruit
4. --Apple extends Fruit
   ----WashingtonApple extends Apple

Coding:
-----------Fruit.java----------
public class Fruit{
}

-----------Mango.java----------
public class Mango extends Fruit {
}

-----------Papaya.java----------
public class Papaya extends Fruit {
}

-----------Apple.java----------
public class Apple extends Fruit {
}

-----------WashingtonApple.java----------
public class WashingtonApple extends Apple {
}

---------------------------------
//Main basic program to run: FruitMain.java

import java.util.ArrayList;
import java.util.List;

public class FruitMain {
    public static void main(String[] args) {
        Mango m=new Mango();
        Papaya p=new Papaya();
        Fruit f=new Fruit();
        //m=f; //Not allowed
        f=m; //Allowed
        f=p; //Allowed
        
        List <Fruit> fruitList=new ArrayList<Fruit>();
        fruitList.add(new Mango());
        fruitList.add(new Papaya());
        System.out.println("List contains: "+fruitList);

        List <Mango> mangoList=new ArrayList<Mango>();
        List <Papaya> papayaList=new ArrayList<Papaya>();
        //mangoList=fruitList;  //Not allowed, you know it is normal.
        
        //fruitList=mangoList;  //Here sub types are not allowed. Generics are not Co-variant.
                                   //But why? If a mango is a fruit, a box of mangoes (a list) is also a 
                                //box of fruits. Because you may later add: fruitList.add(new Papaya());

        //fruitList=papayaList; //Same as above, sub-types are not allowed.
        
        Mango[] mangoArray = new Mango[1];
        Fruit[] fruitArray = mangoArray;  //Arrays are Co-variant.
        fruitArray[0] = new Papaya();  //Compiles but at runtime throws java.lang.ArrayStoreException
        System.out.println("Fruit Array contains: "+fruitArray);
        
        //Because of this behavior of arrays, during a store operation, the Java runtime needs to check 
        //that the types are compatible. The check, obviously, also adds a performance penalty that you 
        //should be aware of. Why above was not allowed because how do you call: sort(fruitArray);
        //Sort will work only when array has same type of values.
    }
}

//Output:
List contains: [Mango@130c19b, Papaya@1f6a7b9]
Exception in thread "main" java.lang.ArrayStoreException: Papaya
    at FruitMain.main(FruitMain.java:30)

Highlights: Sub-Class objects can be assigned to Super classes but same is not true in Generics.    
    f=m; //Allowed
    fruitList=mangoList;  //Here sub types are not allowed. Check comment in program.
    fruitArray[0] = new Papaya();  //Compiles but at runtime throws java.lang.ArrayStoreException
-----------

"? extends T" type Generics:
What "?" stands for? It stands for Unknown type. As in begining I told, Generics are for type-Safety
compilations, we use this to avoid getting ClassCast kind of problems at run time, so loop holes be filtered 
at compilation time only by providing strict type safety. A simple example:

Suppose you want to make a copy of a parameter whose type is Set<?>. You've also been told it's better to use 
Set<?> instead of the raw type "Set" if you don't know the type of the set's contents. So you try this:

    class Foo {
        public void doSomething(Set<?> set) {
            Set<?> copy = new HashSet<?>(set);  //Illegal
        }
    }

Unfortunately, you can't invoke a generic constructor with a wildcard type argument. You should do this:
    class Foo {
        public void doSomething(Set<?> set) {
            Set<?> copy = new HashSet<Object>(set);  //Legal
        }
    }
This is type-safe and will do what you think new HashSet<?>(set) would do. 

Now let's discuss "? extends". Please see the program below, it will be quite clear:

//WildcardMain.java
import java.util.ArrayList;
import java.util.List;

public class WildcardMain {
    public static void main(String[] args) {
        
        //Repeating the array again
        Apple[] appleArray = new Apple[2];
        Fruit[] fruitArray = appleArray;
        fruitArray[0] = new Apple(); 
        //fruitArray[1] = new Papaya();  //Compiles but at runtime throws java.lang.ArrayStoreException
        System.out.println("Fruit Array contains: "+fruitArray);
        
        
        //Now we will use WildCard character: "? extends Fruit" : means List has a sub-type 
        //of Fruit but we don't know what is exact sub-type, so no addition is allowed apart from null.
        
        List<Apple> apples = new ArrayList<Apple>();
        apples.add(new Apple());
        apples.add(new Apple());
        List<? extends Fruit> fruits = apples;
        
        fruits.add(null);
        //fruits.add(new Apple());  //Not allowed, no Compilation.
        //fruits.add(new Fruit());  //This too not allowed, no Compilation.
        //fruits.add(new Mango());  //Not allowed, no Compilation.
        //fruits.add(new WashingtonApple()); //Not allowed, no Compilation.
        System.out.println("Fruit WildCard Array contains: "+fruits);
        
        Fruit f=fruits.get(0);  //This is ok, as for whatever sub-type case, returned object will be Fruit.
        System.out.println("Returned Fruit: "+f);
    }
}
//Output:
Fruit Array contains: [LApple;@923e30
Fruit WildCard Array contains: [Apple@130c19b, Apple@1f6a7b9, null]
Returned Fruit: Apple@130c19b

-----------
"? super T" type Generics:
See the program below, it will be quite clear:

//WildcardSuper.java
import java.util.ArrayList;
import java.util.List;
public class WildcardSuper {
    public static void main(String[] args) {
        List<Fruit> fruits = new ArrayList<Fruit>();
        fruits.add(new Fruit());
        fruits.add(new Fruit());
        System.out.println("Fruit List contains: "+fruits);

        //Making super class of Apple but not sure what super type, So Apple, WashingtonApple is 
        //allowed but not Object which is also super class of Apple.
        List<? super Apple> apples = fruits; 
        System.out.println("Apple Super Wildcard contains: "+apples);
        
        apples.add(new Apple());    //Only Apple or sub-classes of Apple is allowed.
        //apples.add(new Mango());  //Not allowed.
        //apples.add(new Object()); //Only Apple or sub-classes of Apple is allowed.
        //apples.add(new Fruit());  //This is also not allowed. Since we can not know which super type 
                                    //it is, we aren't allowed to add instances of any.

        apples.add(new WashingtonApple());
        System.out.println("Now Apple Super Wildcard contains: "+apples);
        
        Object o=apples.get(0); //Only object will be returned as we don't what super type it is.
        System.out.println("Returned object: "+o);
    }
}
//Output:
Fruit List contains: [Fruit@82ba41, Fruit@923e30]
Apple Super Wildcard contains: [Fruit@82ba41, Fruit@923e30]
Now Apple Super Wildcard contains: [Fruit@82ba41, Fruit@923e30, Apple@7d772e, WashingtonApple@11b86e7]
Returned object: Fruit@82ba41

-----------
Implementation of "? extends" and "? super" in methods:

    static void makeEmpty(List<? extends Fruit> fruits){ 
        System.out.println("Container contents: "+fruits);
    }
        
    Since a List of whichever subtype of the class Fruit is a subtype of List<? extends Fruit>, 
    the previous method will accept any such list as a parameter. Ex:
        
        List<Apple> apples = new ArrayList<Apple>();        
        List<? extends Fruit> fruits = apples;  //Check more detail in above program: WildcardMain.java
        makeEmpty(fruits);         //Allowed as it is same as Fruit class.
        makeEmpty(apples);         //This is already allowed as it extends Fruit.
---------

    static void store(List<? super Fruit> container){ 
        System.out.println("Container contents: "+container);
    }
    
    This way, a List of whichever supertype of Fruit could be passed in to the store function and you could 
    safely put whichever Fruit subtype into it.

    List<Fruit> fruits = new ArrayList<Fruit>();    
    List<? super Apple> apples = fruits;     //Check more detail in above program: WildcardSuper.java    
    apples.add(new Apple());   //Only Apple or sub-classes of Apple is allowed. 
    //apples.add(new Mango());  //Not allowed.
    store(fruits);   //Only Fruit and super type of Fruits are allowed.
    //store(apples); //Not allowed

----------
Implementation of "T" in interfaces:
I don't want to discuss this topic in detail but just giving a sample program and help:
See the List interface in java.util package, below is the declaration:
public interface List<E> extends Collection<E> {}

//My definitions:
public interface JuicyInterface<T> {
    Juice<T> squeeze();
}
-------
public class Juice<T> {
}
-------
public class Orange extends Fruit implements JuicyInterface<Orange> {
    static Juice<Orange> orangeJuice=null;
    
    public Juice<Orange> squeeze() {
        return null;
    }
    public static void main(String[] args) {
        Orange orange=new Orange();
        orangeJuice=orange.squeeze();
        System.out.println(orangeJuice);
    }
}

Summarizing the behavior of the "? extends" and the "? super" wildcards, we draw the following conclusion:
  1. Use the "? extends" wildcard if you need to retrieve object from a data structure.
  2. Use the "? super" wildcard if you need to put objects in a data structure.
  3. If you need to do both things, don't use any wildcard.
--------------------------------END----------------------------------

No comments:

Post a Comment