Generics bad, Autocasting worse?
Sam has a lot of complaints about the forthcoming introduction of generic types into Java. I share almost all of those complaints, and even have a few more of my own which I will try to write about later.
However, Sam goes on to propose an addition to the java compiler which he calls Autocasting. The basic idea is to have the compiler insert a cast for you in an assignment by simply checking the type of the variable to be assigned. This only occurs on assignments - it can't reaonsably work on method call parameters, for example, because of method name overloading.
The only real benefit I can see here is that it occasionally saves you a few keystrokes. I argued with Sam that the downside is code that is harder to understand, which always trumps brevity IMHO. Sam counters that it isn't harder to understand, because a cast in an assignment is redundant - you already have declared the desired type in the variable declaration. Autocasting doesn't hide or obscure anything.
I found it hard to argue with this initially, until I considered the case of a living software system in which lots of people are making changes to source files. In the real world, explicit type casting is an invaluable safeguard against people on different sides of a design contract getting their wires crossed.
The example I presented Sam with was something like this:
public class Pet {}
public class Animal extends Pet {}
public class PetOwner {
public Animal getPet() { return new Animal(); }
public static void main(String[] args) {
PetOwner owner = new PetOwner();
Animal pet = owner.getPet();
}
}
Granted, this is poor design - getPet() should really return a Pet. However, who can say they've never encountered poor design? (never in thier own code, of course ;) ). And say I were irritated enough with the getPet() method to want to go in and clean it up:
public class PetOwner {
public Pet getPet() { return new Animal(); }
public static void main(String[] args) {
PetOwner owner = new PetOwner();
Animal pet = owner.getPet();
}
}
With Sam's Autocasting feature in place, the code in the main() method is going to continue to compile just fine; the Autocasting compiler will recognize that downcasting to 'Animal' from 'Pet' is valid, and so it will go ahead and add the cast for us at compile time. Our code will continue to compile and run just fine, without even touching the main() method at all. Autocasting just saved us a lot of trouble, right?
Well, yes, at least until we do something like introduce a new kind of Pet:
public interface Pet {}
public interface Animal extends Pet {}
public interface Rock extends Pet {}
public class PetOwner {
public Pet getPet() { return new Rock(); }
public static void main(String[] args) {
PetOwner owner = new PetOwner();
Animal pet = owner.getPet();
}
}
Now our code compiles just fine, but we are due for a nasty ClassCastException at runtime, one which will be all the more vexing because the line of code in question contains no cast.
Granted, this would not be a problem if we'd simply gotten our abstractions right on the first try. Actually, Sam probably does get all of his abstractions right on the first try - that's why he came up with Autocasting. :)
Unfortunately, the rest of us sometimes need to shift things around a little bit, at least early on in the development cycle. I think we need to hang on to all of the help that the compiler can give us.
