based on original article: http://java.sun.com/j2se/1.5/pdf/generics-tutorial.pdf
========================================
List<Integer> myIntList = new LinkedList<Integer>(); // 1’
myIntList.add(new Integer(0)); //2’
Integer x = myIntList.iterator().next(); // 3’
When we say that myIntList is declared with type List<Integer>, this tells us something about the variable myIntList, which holds true wherever and whenever it is used, and the compiler will guarantee it.
public interface List<E> {
void add(E x);
Iterator<E> iterator();
}
public interface Iterator<E> {
E next();
boolean hasNext();
}
You might imagine that List<Integer> stands for a version of List where E has been uniformly replaced by Integer:
public interface IntegerList {
void add(Integer x);
Iterator<Integer> iterator();
}
This intuition can be helpful, but it’s also misleading. It is helpful, because the parameterized type List<Integer> does indeed have methods that look just like this expansion. It is misleading, because the declaration of a generic is never actually expanded in this way. There aren’t multiple copies of the code: not in source, not in binary, not on disk and not in memory. If you are a C++ programmer, you’ll understand that this is very different than a C++ template. A generic type declaration is compiled once and for all, and turned into a single class file, just like an ordinary class or interface declaration.
List<String> is not List<Object>, however String is an Object.
List <String> ls=new ArrayList<String>(); // correct
List <Object> lo=ls; // wrong, compile time error
// if it wasn’t compile time error--
lo.add(new Object());
String s=ls.get(0); // s is not string, isn’t it
In general, if Foo is a subtype (subclass or subinterface) of Bar, and G is some generic type declaration, it is not the case that G<Foo> is a subtype of G<Bar>.
With that said, List<SubClass> "is-not-a" List<BaseClass> (and off course vice verse), answer following questions--
1) Can you add SubClass object to List<BaseClass> ? (This make sense because SubClass "is-a" BaseClass)
2) Can you add BaseClass object to List<SubClass> ? (well, That's flat-out wrong since BaseClass "is-not-a" SubClass)
3) Can you new a ArrayList<SubClass> in List<BaseClass> ? (off course we should because SubClass "is-a" BaseClass)
lets try it--
using 1.6.0_10 javac
import java.util.*;
class Bar {}
class Foo extends Bar {}
public class GenTest {
public static void main(String[] args){
/*********************Question # 2
List<Foo> list=new ArrayList<Foo>();
list.add(new Bar());
causes following compile time error
====================================
GenTest.java:9: cannot find symbol
symbol : method add(Bar)
location: interface java.util.List<Foo>
list.add(new Bar());
**********************/
// Question # 1// this is good
List<Bar> list=new ArrayList<Bar>();
list.add(new Foo());
/**********************Question # 3
// but you can not new ArrayList<Foo> in List<Bar>
// where Foo "is-a" Bar, why ? because of folllowing--// you can add objects of other subclass of Bar
// to list, which is new'ed with ArrayList<Foo>.
// Hence, if you need ArrayList<Foo> put it in List<Foo>
List<Bar> list=new ArrayList<Foo>();
causes following compile time error
====================================
GenTest.java:28: incompatible types
found : java.util.ArrayList<Foo>
required: java.util.List<Bar>
List<Bar> list=new ArrayList<Foo>();
**********************/
}
}
Wildcards
// 1
void printCollection(Collection c) {
Iterator i = c.iterator();
for (k = 0; k < c.size(); k++) {
System.out.println(i.next());
}
}
Generic version
// 2
void printCollection(Collection<Object> c) {
for (Object e : c) {
System.out.println(e);
}
}
Generic version is less useful because Collection<Object> isn’t Collection of anything, for the same reason discussed above.
How can we write collection of anything in generics? Here comes wildcards.
// 3
void printCollection(Collection<?> c) {
for (Object e : c) {
System.out.println(e);
}
}
And now we have it, how it is different then of first non-generic printCollection ?
In 3rd printCollection with wildcard ? you can get from collection but can not put into the collection, since type is unknown you may end corrupting the collection if you could put. (Question: how does compiler knows that ‘add’ is going to put into the collection? Anyone?). On the other hand, you can always get an unknown type from collection because its type is always going to be Object.
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // compile time error
GOTCHA: you can still put ‘null’ into the collection; fyi null doesn’t have a type.
Well that’s the difference between 1st printCollection and 3rd printCollection.
Bounded Wildcard
Upper bounded: <? extends MyClass> any type that extends MyClass and MyClass itself
Lower bounded: <? super MyClass> all super types of MyClass and MyClass itself
When using upper bounded wildcard you can not put into Type, all you can do is get (except null off course which doesn’t have type). Why put is not allowed? Well, since actual type is unknown and all we know that base class is MyClass somewhere in class hierarchy, you can go across class hierarchy. For ex—
public void addSometing(List <? extends MyClass> list)
{
list.add(0, new Foo()); // wrong, compile time error
// where my ‘list’ is suppose to be of ‘Bar’s
}
Class hierarchy—
MyClass
/ \
Foo Bar
And now you have corrupted the list that is passed in addSomething because when I do get
Bar bar=list.get(0);
// guess what, I got ClassCastException at runtime, if compiler
// would have allowed statement in addSomething.
And that’s what I meant by across the class hierarchy.
What if I want to write something like addSomething, can I still use generics and all good comes with it?
Yes, you have to add generic type to your method and as well as parameter, here is how—
public <T> void addSomething(List<T> list)
{
// And you (at least I did) would immediately tend to do that
list.add(new T()); // wrong, compile time error “unexpected type”
}
I was hoping from how I call addSomething compiler should be able to figure out if that “type” has a no-argument constructor, and let me do this unless I’m trying to call addSomething with something that doesn’t have no-arg ctor. But it doesn’t work that way in Java, I guess my c++ template background tempted me to think like that. Then how should I write addSomething?
public <T> void addSomething(T t, List<T> list)
{
list.add(t); // correct, used a pre-constructed object of T
}
But what if I don’t want to pass pre-constructed object, believe me java will find some hack way of doing this and call it design pattern and tell us how it is better then c++. I don’t know how at this point, let see if we can find out at some point.
So when should I use generic method and when wildcard?
Generic methods allow type parameters to be used to express dependencies among the types of one or more arguments to a method and/or its return type. If there isn’t such a dependency, a generic method should not be used.
For ex:
interface Collection<E> {
public boolean containsAll(Collection<?> c);
public boolean addAll(Collection<? extends E> c);
}
Since there is no “type” relationship
1) between the arguments and/or
2) between argument(s) and return type
use wildcard.
class Collections {
public static <T> void copy(List<T> dest, List<? extends T> src){...}
}
There is “type” relationship between the arguments.
We could write this as:
class Collections {
public static <T, S extends T> void copy(List<T> dest, List<S> src){...}
}
This is fine, but while the first type parameter is used both in the type of dst and in the bound of the second type parameter, S, S itself is only used once, in the type of src - nothing else depends on it. This is a sign that we can replace S with a wildcard. Using wildcards is clearer and more concise than declaring explicit type parameters, and should therefore be preferred whenever possible.