What if you wanted to have a List or Collection of items, but with completely different types? Obviously, the standard way to deal with this would be with simply a List
The real solution to this issue is to take a step back and ask why you want a list like this instead of a class encapsulating it with several members of different types… but there actually is a way to do this within the java type system that is completely ugly and terrible, but perhaps somewhat interesting:
public abstract class TypedList<A, B extends TypedList<?,?>> {
public abstract A head();
public abstract B tail();
public abstract boolean isEmpty();
public static <A, B extends TypedList<?, ?>> TypedList<A, B> cons(A a, B b) {
return new SomethingList<A, B>(a, b);
}
public static NothingList getEmptyList() {
return NothingList.getInstance();
}
private static class NothingList extends TypedList<Void, NothingList> {
private static final NothingList nl = new NothingList();
public static NothingList getInstance() { return nl; }
private NothingList() {}
@Override
public Void head() {
throw new UnsupportedOperationException();
}
@Override
public NothingList tail() {
return this;
}
@Override
public boolean isEmpty() {
return true;
}
}
private static class SomethingList<A, B extends TypedList<?, ?>> extends TypedList<A, B> {
private A elem;
private B rest;
public SomethingList(A a, B b) {
elem = a;
rest = b;
}
@Override
public A head() {
return elem;
}
@Override
public B tail() {
return rest;
}
@Override
public boolean isEmpty() {
return true;
}
}
}
Basically, this is a functional-style linked list, with several additional type parameters. The first parameter is the type of the first element in the list, and the second parameter is the type of the rest of the list. With this, you can commit such atrocities as:
TypedList<String, TypedList<Double, TypedList<String, TypedList<ArrayList<String>, NothingList>>>> list =
cons("hello!", cons(3.14, cons("world!", cons(new ArrayList<String>(), TypedList.getEmptyList()))));
String first = list.head();
Double second = list.tail().head();
String third = list.tail().tail().head();
ArrayList<String> fourth = list.tail().tail().tail().head();
Here, we have a list containing first a String, then a Double, then a String, and then an ArrayList, and creating and accessing the elements in this list are all type-safe operations. Of course, the downside is that in order to get the type-safety, you must explicitly state the type of every element of the list ahead of time, which is impossible with variable-length lists. But if we want a list of arbitrary length, we can certainly abandon type-safety:
// build the list
TypedList<?,?> list = TypedList.getEmptyList();
for (int i = 0; i < 10; i++) {
list = cons(i, list);
}
// print out (and destroy) the list
while (!list.isEmpty()) {
System.out.println(list.head());
list = list.tail();
}
And we can even mix the two approaches (this code makes me cringe):
TypedList<?,?> list = TypedList.getEmptyList();
for (int i = 0; i < 10; i++) {
list = cons(i, list);
}
TypedList<String, ?> stringHeadedList = cons("Hello, ", list);
Ugh. And how about this class header…
public class TypedTree<A, L extends TypedTree<?, ?, ?>, R extends TypedTree<?, ?, ?>>
Or this…
public class TypedMap<K, V, A extends TypedMap<?, ?, ?>>
Basically, there are a lot of truly awful things you can do with Java generics; maybe I’ll eventually get around to a post about the good, useful things.