First of all, here's the behavior added by Java 7's Collections.checked*:
Since null is considered to be a value of any reference type, the returned collection permits insertion of null elements whenever the backing collection does.
This doesn't appear to be listed in the compatibility documentation, though. Demo:
public class MyAPI {
private Set<Polygon> polygons = new Collections.checkedSet(new HashSet<Polygon>(), Polygon.class);
public Set<Polygon> getPolygons() {
return polygons;
}
}
public class MyAPITest {
// This JUnit test passes when using Java 6 or earlier, but fails for Java 7.
@Test(expected=NullPointerException.class)
public void testAddNullPolygon() {
new MyAPI().getPolygons().add(null);
}
}
So as you can see I'm writing an API that exposes a Set
for client code to populate. From what I've read, this is one of the use cases for Collections.checkedCollection
etc: the added runtime check helps prevent weird stuff from getting inserted.
I've changed my API to handle nulls regardless, but my concern is that the client code could sometimes throw the NPE, sometimes not, depending on what version of Java the end user was running. That just feels broken. Ideally I'd like to preserve the old behavior, preventing nulls at insertion time.
I guess my options are:
Give up on runtime checking entirely.
Not worry about it, and trust client code to never insert nulls.
Declare that my API only supports JRE 7.
Use Guava, which looks great but is an extra dependency my API would be saddled with.
Roll my own Set wrapper that enforces null and type checking.
Some other more elegant solution that I'm missing.
Any guidance would be much appreciated!
The option that I ultimately went with is to get rid of the checkedCollection, making the exposed collection unmodifiable. Users of the API need to call one of the extra add/remove/clear methods to modify the collection.
This limits API users somewhat: they can't, for example, use addAll to copy all elements of another collection in a single method call. But it's a fair tradeoff for simplicity and type safety.
public class MyAPI {
private Set<Polygon> polygons = new HashSet<Polygon>();
private Set<Polygon> polygonsReadonlyView = Collections.unmodifiableSet(polygons);
public Set<Polygon> getPolygons() {
return polygonsReadonlyView;
}
public boolean addPolygon(Polygon p) {
if (p == null) {
throw new IllegalArgumentException("polygon cannot be null");
}
return polygons.add(p);
}
public boolean removePolygon(Polygon p) {
return polygons.remove(p);
}
public void clearPolygons() {
polygons.clear();
}
}
One takeaway from all this is to never rely on a checkedCollection to prevent nulls from being inserted.
A Google search might suggest this... but don't do it! Null-checking is not one of checkedCollection's use cases.