Difference between revisions of "Generics"

From Suhrid.net Wiki
Jump to navigationJump to search
Line 155: Line 155:
 
* List<?> list is different from List<Object> objList ! The second will take only lists which contain Objects, whereas the former will take anything.
 
* List<?> list is different from List<Object> objList ! The second will take only lists which contain Objects, whereas the former will take anything.
 
* It follows that List<? extends Object> is the same as List<?>
 
* It follows that List<? extends Object> is the same as List<?>
 +
 +
 +
* Wildcards can also be used to declared bounded variables.
 +
 +
e.g. List<? extends Number> genNumList;
 +
//The following are then legal:
 +
 +
genNumList = new ArrayList<Integer>();
 +
genNumList = new ArrayList<Float>();
  
 
== Generic Declarations ==  
 
== Generic Declarations ==  

Revision as of 07:55, 30 July 2011

Introduction

  • Generics are a way of providing type information for Abstract Data Types
  • Generics is a way to enforce ONLY compile-time type safety. Generics are implemented in the compiler only, the JVM has no clue about the use of generics.
  • All the type information is not present at run-time. The compiler strips out type information from the bytecode using a process called type erasure.
  • WHY Type erasure ? To ensure backward compatibility with legacy code.
  • This compile-time safety is broken when generic and non-generic legacy code are mixed up.

See below:

       private void bar() {
		List<Integer> li = new ArrayList<Integer>();
		li.add(new Integer(1));
		li.add(new Integer(2));
		foo(li);
		for(Integer i : li) {
			System.out.println(i.intValue()); // This will fail with a ClassCastException.
		}
	}
	
	private static void foo(List l) {
		l.add(new Integer(3));
		l.add(new String("4")); //Compiler ALLOWS this !  However, warning will be generated.
	}
  • Watch out when autoboxing is involved with legacy code.
List l = new ArrayList();
l.add(123); //Auto-boxing happens.
int i = l.get(0); //Compile-time error. Autounboxing cant work because get() returns Object and not Integer.

Generics and Raw Types

  • It is possible to use a generic class by its raw type only. e.g. instead of using List<String> using a plain List.
  • The compiler will issue an unchecked warning when it detects that there could be a problem at runtime when using the raw type.
  • Compiler provides more details when the -Xlint:unchecked option.

Polymorphism and Generics

  • This is OK:
List<Number> nos = new ArrayList<Number>();

nos.add(new Integer(10));
nos.add(new Double(20.2));
List<String> list = new ArrayList<String>();

However : Polymorphism only applies to the base type i.e list can be declared as arraylist You CANNOT do this:

 List<Animal> obj = new ArrayList<Dog>(); //NOT POSSIBLE

WHY ?

To prevent scenarios where you cannot add say, a Cat object to a Dog List. If the above conversion were possible it will be possible to do so. See below:

//NOTE : This is not possible actually, because the compiler prevents it.
public void foo() {
   List<Dog> dList = new ArrayList<Dog>();
   addAnimal(dList); //Compiler flags an error here. a Dog list cannot be assigned to an Animal list
}

private void addAnimal(List<Animal> aList) {
  aList.add(new Cat());
}

However, the SAME thing is possible with Arrays

public void foo() {
   Dog[] dA = new Dog[]{};
   addAnimal(dList); 
}

private void addAnimal(Animal[] aa) {
  aa[0] = new Cat(); //This will cause a runtime ArrayStoreException
}
  • The reason why it such polymorphism is possible with Arrays but not with collections is because of Type Erasure - Since there is no type information at run-time, JVM cannot raise an exception.
  • List<Dog> is not a subtype of List<Animal> - they are treated as different types. In case of arrays Dog[] is a subtype of Animal[] - because the type information is preserved at runtime.
  • This will be exactly the same problem when type-safe collections are mixed with non-type safe ones.
  • So, the compiler will prevent such polymorphic assignments when we are dealing with type-safe collections.

Q : How can generic collections be used polymorphically ? A : Use wildcards !

  • See example below. This means all Lists of Dogs, Cats, Elephants etc can be passed to the move method.
  • However the compiler ensures that the list cannot be modified to avoid wrong animals being inserted into the wrong lists.
  • How does the compiler do that ? It disallows any method in the List interface which accepts a generic parameters like add(E) or bar(E).
       public static void foo() {
		List<Dog> ld = new ArrayList<Dog>();
		foo(ld);
	}
	
	private static void move(List<? extends Animal> wildL) {
		for(Animal a : wildL) {
			a.move();
		}
	}


  • If you do want to modify the list, the wildcard declaration must be made safe by using the keyword super.
  • In the below example, it means that all Lists containing objects of superclasses of Dog can be passed to the foo method.
  • So foo is safe to add Dog objects to the passed list, since the passed list is guaranteed to be a list of some objects which are above Dog. So type-safety is maintained.
       public static void bar() {
		List<Dog> ld = new ArrayList<Dog>();
		foo(ld);
	}
	
	private static void foo(List<? super Dog> dogList) {
              dogList.add(new Dog()); //OK
              dogList.add(new Cat()); //OK 
              dogList.add(new Animal()); //WONT WORK. 
              dogList.add(new Object()); //WONT WORK.
	}
  • Why does the compiler not allow the addition of an animal object or a plain old object here ? Because List<? super Dog> says Lists containing objects of super-types of Dogs can be passed to this method. which means that we could pass a List<Animal> to this method as well. So, if the compiler allowed a plain Object to be added to a List<Animal> it wont make sense. So any objects of the super type are not allowed to be added to the list.
  • NOTE VERY IMPORTANT: Wildcards specify what kind of collections the method can accept. NOT what can be added to the collection
  • So try to look at the wildcard syntax as what kind of parameterized types it allows and then see what objects will make sense to be added to the ADT.
  • Only using the wildcard - like below, means that any list can be passed to it. But of course nothing can be added to the list.
        void foo(List<?> anyList)
  • List<?> list is different from List<Object> objList ! The second will take only lists which contain Objects, whereas the former will take anything.
  • It follows that List<? extends Object> is the same as List<?>


  • Wildcards can also be used to declared bounded variables.

e.g. List<? extends Number> genNumList; //The following are then legal:

genNumList = new ArrayList<Integer>(); genNumList = new ArrayList<Float>();

Generic Declarations

  • Generic class. See example below:
  • The Generic type T's object (t) is treated only as a regular Object. (Obviously, since at compile-time, we are unaware of its type)
class Node<T> {
	private T t;
	
	public Node(T t) {
		this.t = t;
	}
	
	public T getNode() {
		return t;
	}
	
	public String toString() {
		return t.toString();
	}
	
}

public class GenericDeclarations {

	public static void main(String[] args) {
		Node<Integer> ni = new Node<Integer>(1); 
                Node<JButton> njb = new Node<JButton>(new JButton());
	}
	
}
  • An invocation of a generic type is called a parametrized type like Node<Integer>. The compiler treats a parametrized type as a new type.
  • Wildcard types cannot be used in the header of a generic class to restrict the type. This is ILLEGAL:
class ComparableThings<? extends Comparable> { //ILLEGAL
}
  • Instead the correct way to restrict the parameterized types to be Comparables is as below:
class ComparableThing<E extends Comparable<E>> { //OK

}
  • The compiler will now only permit ComparableThing<Integer> i = new ComparableThing<Integer>() and not ComparableThing<Thread> t = new ComparableThing<Thread>();
  • Note: "T" is a convention used to indicate a Generic type. However you can use any identifier like
class Node<Type> { }
//Note, you can legal Java classes as identifiers too ! This produces something ABSURD like:
class Node<String> { }
//This does not mean Node of Strings!! rather String identifer "hides" the class name - it can be used as a generic type, like 
Node<Thread> nT = new Node<Thread>();
  • Gotcha's:
    • A constructor cannot specify the generic type. e.g. Node<T>() {} is not legal.
    • A type parameter cannot be used to create a new object. e.g. T t = new T() is not legal.
    • A type parameter is non-static. It cannot be used in a static context. Why ? Because like instance variables the type parameter is associated with objects.

Generic Methods

  • The class need not be generic. Individuals method can accept generic types.
  • The generic type has to be specified BEFORE the return type of the method. See below
class Node {
	
	StringBuffer nodeName = new StringBuffer("node");
	
	public <T> void append(T t) {
		nodeName.append(": " + t.toString());
	}
}


public class GenericDeclarations {

	public static void main(String[] args) {
		Node n = new Node();
		n.append(new JButton());
		n.append(new Thread());
		
		System.out.println(n.nodeName);
	}
	
}
  • This version will accept only Numbers
public <T extends Number,U extends Number> void addInt(T t, U u) {
		Number n1 = (Number) t;
		Number n2 = (Number) u;
		System.out.println(n1.intValue() + n2.intValue());
}
  • Note the following method declaration is NOT generic, it assumes there is a class called "T" that is available somewhere
     public void foo(T t) { }
  • See the below method in Collections class (simplified) which returns a reverse order comparator for a given collection.
     public static <T> Comparator<T> reverseOrder() {
        return (Comparator<T>) REVERSE_ORDER;
    }

    private static final Comparator REVERSE_ORDER = new ReverseComparator();

    private static class ReverseComparator<T> implements Comparator<Comparable<Object>> {

        public int compare(Comparable<Object> c1, Comparable<Object> c2) {
            return c2.compareTo(c1);
        }
    }
  • Note that type T will be the type of the objects contained in the collection. See below: Here T will be Integer.
  • Collections.sort takes (List<T>, Comparator<? super T>) as the two parameters. Now, Collections.reverseOrder() returns a Comparator<T> which will be used as Comparator<Integer>
  List<Integer> marks = new ArrayList<Integer>();
  Collections.sort(marks, Collections.reverseOrder());