Wednesday, December 23, 2015

Cast Away

Java Generics are neat and all, but one situation I find them highly annoying goes like this:
  1. An interface IBar and an interface IFoo with a method:

    public Set<? extends IBar> getBars ();
     
  2. Bar is a concrete implementation of IBar, and Foo is a concrete implementation of IFoo that gives a covariant return for the same method:

    public Set<Bar> getBars ();
     
  3. Another method that declares it returns a Set<? extends IBar> where I know the Set actually holds instances of Bar.
     
  4. The implementation of Foo.getBars() calls this other method and intends to return the Set.
     
  5. The compiler balks because this is not acceptable:

    Set<? extends IBar>() bars = x();return (Set<Bar>) bars;
Indignantly I balk because this cast should be fine! But in reality, the nature of how Java Generics works does not allow downcasting of parameterized types. The bytecode has no knowledge of these types and no way to check them at runtime.

An easy hack would be to subvert the Java Generics compiler checks, such as:

Set<? extends IBar>() bars = x();
return (Set<Bar>)(Set<?>) bars;

For those who don't care about type-safety, I'm sure you will jump at that solution. For those who treasure type-safety, though, this option is highly unpalatable.

For a type-safe solution, a direct cast is not acceptable, so a copy with the correct type must be generated. To provide a single method that would satisfy this requirement for any Collection type took a bit of finagling of both Java Generics and reflection. This is what I finally came up with:

public static <T,
               U extends T,
               C extends Collection<? extends T>,
               D extends Collection<U>>
 D specializedCollection (C generalized, Class<? extends U> type) {
    try {
        D specialized = (D) generalized.getClass().newInstance();
        for (T item : generalized) {
            if (type.isInstance(item)) {
                specialized.add((U)item);
            }
            else {
                throw new ClassCastException("Element not castable to " + type.getClass() + ": " + item);
            }
        }
        return specialized;
    } catch (IllegalAccessException | InstantiationException e) {
        throw new IllegalArgumentException("Could not create a " + generalized.getClass().getName(), e);
    }

}
  • The U parameterized type had to be passed into the method because it cannot be discovered via the D type, or the C instance provided.
  • Collections are supposed to provide a public no-argument constructor, so the use of reflection to create the new instance is acceptable.
  • The cast of item from a type T to a type U is an acceptable cast because by the types declared, U is a subtype of T. However, this cast does not occur in the bytecode since parameterized types are deleted upon compilation.
  • In order to ensure type safety, Class.isInstance() is explicitly called using the U parameterized type in order to generate the expected ClassCastException.
Now I can maintain type safety with a single method call such as:

Set<? extends IBar>() bars = x();
return specializedCollection(bars, Bar.class);

Less efficient, sure, but now I am type safe!

This post is also available at Quantum Strides, and while you're there check out my Java Fundamentals course!

No comments:

Post a Comment