Generics: All or nothing?

I am a big fan of strong typing, so when Java 5 added support for generics, I started using them heavily. I don’t agree that that they “clean up” your code, because I see as many or more instances of type parameters, <Class>, as I used to see type casts, (Class). However, they do make code easier to read and follow.

I’ve also been using interfaces more and more over the years, and these days I’d say I use them on almost all of my beans. The lightweight multiple inheritance helps with organization, and when you’re dealing with libraries that monkey with the internal working of your code like proxied beans in Spring and Hibernate objects, interfaces are helpful if not required.

A while back, I inadvertently discovered a pitfall that had been plaguing a project of mine in a very confusing manner. Parameterizing a class should avoid class cast exceptions because of strong typing, but you have to be careful to keep it parameterized, or you can end up causing some very nasty runtime bugs. To illustrate, here is a simple example:

(forgive the formatting, WordPress’ editor is poor at handling code…

First we have a Person interface. People have IDs and vehicles.

package com.efsavage.generic;
import java.util.Set;
public interface Person<PK extends Comparable> {
public PK getId();
public void setId(PK id);
public Set<Vehicle> getVehicles();
public void setVehicles(Set<Vehicle> vehicles);
}

We also have a Vehicle interface, the fields of which are unimportant here:

package com.efsavage.generic;
public interface Vehicle { }

And we have an implementation of the Person interface:

package com.efsavage.generic;
import java.util.Set;
public class PersonImpl implements Person {
private Integer id;
private Set vehicles;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Set getVehicles() {
return vehicles;
}
public void setVehicles(Set vehicles) {
this.vehicles = vehicles;
}
}

So now let’s write a little test:

package com.efsavage.generic;
import java.util.Set;
public class PersonTest {
public static void main(String[] args) {
Person me = new PersonImpl();
Person<Integer> you = new PersonImpl();
me.setId("5");
you.setId(5);
Set<String> myVehicles = me.getVehicles();
Set<Vehicle> yourVehicles = you.getVehicles();
}
}

This code compiles fine (though Eclipse will flag a few warnings). There’s two problems here.

Despite the fact that PersonImpl implements Person<Integer>, I’m able to set a string via me.setId(“5”), because I did not keep that parameter when I cast me to Person, without a parameter. I would have expected the parameter to be inferred here.

The other problem is that I’m able to cast myVehicles to Set<String>! This wasn’t even a parameter I defined, it’s right in Person. However, by using Person without the PK parameter, Java ignores all other parameters too!

I’m sure someone thought this was a good idea, and there’s a reasonable explanation deep in the JCP forums, but it just seems wrong to me, so be careful to check your parameters if you find weird class cast exceptions showing up in your logs.