One of the biggest additions since the creation of the Collections framework is Generics.This long awaited update to the type system is a welcomed feature, which C++ developers have had in their toolbox for years using the STL. A generic type is defined using one or more type variables with it's contained methods using that type variable as a place holder to mark a parameter or return type. For instance the interface List has now been defined as.
Code:
public interface List<E>{
public add(E e);
Iterator<E> iterator();
}
public interface Iterator<E>{
E next();
boolean hasNext();
}
Type Safe Collections
So you might ask. What are Generics and why should I use them? Generics are a way to restrict a data structure to hold only a specific type thus providing compile time type checking. One of the added bonuses is that it is no longer necessary to cast a returned Object to a specific type because the compiler is aware of what type is being held by the Collection and what type is to be returned.
Code:
Set s = new SortedSet();
s.add(new String("Java"));
String j = (String) s.get(0); // cast required;
// java 5
Set<String> s = new SortedSet<String>();
s.addElement(new String("String Type"));
String s = s.get(0); // no cast required!
Having a type safe system not only obviates the need to cast to a specific type but shields the programmer against miss-casting or casting to an unrelated type at runtime.
Code:
String s = "Java, write once run anywhere";
List l = new ArrayList();
l.add(new String("Java"));
Integer i = (Integer)l.get(0); // Runtime exception! ClassCastException!
Code:
// Using Generics the compiler catches
String s = "Java. Write once run anywhere!";
List<String> l = new ArrayList<String>();
l.add(new String("Java"));
Integer i = l.get(0);
Generics and Subtyping
Generics do not share some of the things that are commonly held true in the Java language and one of them is subtyping. One would assume the following perfectly legal since it is true that Object is a supertype of String. But the following is in fact illegal and will cause a compile time error.
Code:
List<Object> l = new ArrayList<String>();
As a rule. If X is a superclass or superinterface of Y and E is a generic type declaration then it not the case that E<X> is a supertype of E<Y>. So if E<Object> is not a super type of E<String> then what is the super type of E<String>? Read on and find out!
Wild Cards
Wild Cards represent what is called "the unknown type". Essentially they can be thought of as the supertype of any Collection. Wildcards allow some flexibility in certain situations where it is needed that a type be abstracted out. For instance what if we define the following method, printSet(Collection<Object> x). We just saw in the previous example that E<Object> is not the super type of E<String> or any other type for that matter. In this case we can change the printSet's parameter to Collection<?>. This allows us to pass in E<X> where X is any type.
Code:
public void printElements(Collection<?> c){
for(Object o: c){
System.out.println(o);
}
}
When working with wildcards it is always legal to read and assign the contents to an Object type
Code:
List<String> l = new ArrayList<String>();
l.add(new String("Java"));
Object o = getElement(l, 0); // legal
public Object getElement(List<?> l, int index){
Object o = null;
try{
o = l.get(0);
}catch(IndexOutOfBoundsException e){
//.......
}
return o;
}
assigning values is another matter. The add() method takes an argument of type E which is the type that the Collection is to hold. Any type wishing to be added to the Collection would have to be of the same type. Since<?> represents an unknown type it is impossible to determine what the type parameter of the collection represents.
Bounded Wildcards
A Bounded Wildcard allows as type to be constrained. Bounded Wildcards are useful when there is some type of partial knowledge about the type arguments. While it is still illegal to try and add an element to a unknown Collection with a bounded type they come in handy in other situations. One use is to be able to pass not only types into a methods but sub-types also. In doing this we are able to implement polymorphic behavior.
Code:
import java.util.*;
class Printer{
public void print(String s){
for(int i = 0; i < s.length(); i++){
System.out.print(s.charAt(i));
}
}
}
class ReversePrinter extends Printer{
public void print(String s){
for(int i = s.length() - 1 ; i >= 0; i--){
System.out.print(s.charAt(i));
}
}
}
public class G{
public static void main(String[] args){
String s = "Nothing like a good cup of Java in the morning!";
List<Printer> l = new ArrayList<Printer>();
l.add(new Printer());
printElements(l,s);
List<ReversePrinter> rl = new ArrayList<ReversePrinter>();
rl.add(new ReversePrinter());
printElements(rl,s);
}
public static void printElements(List<? extends Printer> l, String s){
Printer printer = l.get(0);
printer.print(s);
System.out.println();
}
}