Saturday, 7 July 2007

Java Specifics

When I first started playing with Java 1.5, I thought generics were the best thing since sliced bread. No more untidy casting, lovely type-safe Collections, and when combined with the new for loop, a lot of the tedious tasks associated with Collections became easier and, most importantly, aesthetically pleasing.



Consider the old code:


List list = new ArrayList();
list.add(new Integer(1));
Integer integer = (Integer)list.get(0);

for (Iterator i = list.iterator(); i.hasNext(); ) {
Integer number = (Integer)i.next();
number.intValue();
}


And the new:

List<Integer> list = new ArrayList<Integer>();
list.add(new Integer(2));
Integer integer = list.get(0);

for (Integer number : list) {
number.intValue();
}


See? Much prettier. OK so it's a silly little example but when you apply it to all the places you use things like Collections it does make life a lot easier, especially when you consider that now you *know* what's in that List that comes back from some method that you're not familiar with.



Nearly two years on from the first time I started using them, I run into an issue - they're not generic at all. They're specific. The point about generics as far as I can figure out is type-safety - they remove the need to cast everything everywhere. Which is great, until you actually want a little flexibility in your types. So, you can have the code above which adds and retrieves Integers from a list, knowing that it's perfectly fine to get objects from that list and treat them as Integers because that's exactly what they are. But what you can't do is assign a List of some subtype to a List of one if its supertypes:


public List<Integer> getIntegers() {
List<Integer> integers = new ArrayList<Integer>();
integers.add(new Integer(1));
return integers;
}

...

public void assignList() {
List<Number> numbers = getIntegers(); //compiler error - Type mismatch
//or
numbers = new ArrayList<Integer>(); //compiler error - Type mismatch
}


OK fine, it's a bit confusing but it makes sense if you think about it. A good example is provided in this article, which explains that if you're expecting a Collection of Numbers, you might expect to be able to add a Float to it, but if lurking under the covers you've assigned a List of Integers to it, it is incorrect to add a Float to it.



So what about wildcards? Aren't they supposed to overcome this issue? Well, sort of. Wildcards in generics means you can do the following:


List<? extends Number> numbers = getIntegers();
//or
numbers = new ArrayList<Integer>();


and this will not give a compiler error. But then you can't do:


List<? extends Number> numbers = new ArrayList<Integer>();
numbers.addAll(new ArrayList<Integer>()); //compiler error
numbers.add(new Integer(1)); //compiler error


So you can use a wildcarded Collection to represent that you know that Collection is going to be some Collection that contains something that is a subclass of Number (for example), but not to say that the Collection can contain any mix of items that are a sublasses of number.



OK... what do I need to remember to prevent confusion in future?



Well, looks like we use:

List<Number> numbers = new ArrayList<Number>();
numbers.add(new Integer(1));
numbers.add(new Double(1));

if you want your collection to contain a mix of items that are subclasses of your specified generic type. But you might have to use something like addAll to insert the contents of some other List into it, as you can't assign Lists of subclasses to it.



If you want to assign Lists that have a generic type that's a subclass, then you want to use wildcards:

List<? extends Number> numbers = getIntegers();
//or
numbers = new ArrayList<Integer>();


So, easy then. No idea what I got so confused about.

No comments:

Post a Comment