Two 〈Foo〉s Walk into a 〈Bar〉: Featuring the Highest Angle Bracket–to–Word Ratio Among Site Titles

Two Java Type‐System Holes

That is, two bugs in the language specification that allow a program to compile without warnings but to produce a ClassCastException at runtime on a line with no explicit cast.

Cast to Inner Class of Generic Type

Code

class Outer<E> {
  Inner inner;

  class Inner {
    E e;

    E getOtherElement(Object other) {
      if (!(other instanceof Outer.Inner)) {
        throw new IllegalArgumentException(String.valueOf(other));
      }

      Inner that = (Inner) other;
      return that.e;
    }
  }

  public static void main(String[] args) {
    Outer<String> s = new Outer<String>();
    s.inner = s.new Inner();
    s.inner.e = "hello";

    Outer<Integer> i = new Outer<Integer>();
    i.inner = i.new Inner();
    i.inner.e = 1234;

    String producesClassCast = s.inner.getOtherElement(i.inner);
  }
}

Output

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
  at Outer.main(Outer.java:26)

Explanation

The Java language specification lacks rules about casts to inner classes. Commonly used versions of javac happen to allow us to cast to Inner — that is, to cast to Outer<E>.Inner — without a warning. The more honest version of the cast, (Outer<E>.Inner), ought likewise to be permitted with a warning; it's rejected outright.

This subtle interaction between generics and inner classes pales in comparison to...

Generic "Overrides" of Static Methods

Code

import java.util.HashSet;
import static java.util.Collections.singleton;

class Printer {
  static <E> void print(Iterable<E> input) {
    for (E e : input) {
      System.out.println("Unsorted " + e);
    }
  }
}

class SortedPrinter extends Printer {
  static <E extends Comparable<? super E>> void print(Iterable<E> input) {
    for (E e : input) {
      System.out.println("Sorted " + e);
    }
  }
}

class Caller {
  public static void main(String[] args) {
    SortedPrinter.print(singleton(1));
    SortedPrinter.print(singleton(null));
    SortedPrinter.print(singleton(new HashSet<Object>()));
  }
}

Output

Sorted 1
Sorted null
Exception in thread "main" java.lang.ClassCastException: java.util.HashSet cannot be cast to java.lang.Comparable
  at SortedPrinter.print(Caller.java:14)
  at Caller.main(Caller.java:24)

Explanation

You need two obscure pieces of knowledge to understand this one:

  1. At runtime, methods are identified by these four pieces of information:
    • return type
    • class name
    • method name
    • argument types
    (Note the absence of anything related to type parameters.)
  2. Indirect references to static methods are compiled oddly:
    class Base {
      static void foo() {}
    }
    
    class Derived extends Base {
    }
    The bytecode for a call to Derived.foo() is "void Derived.foo()," not "void Base.foo()." The VM knows to look for the method first in Derived and then in Base.

So: The compiler can see that Collection<HashSet<Object>> does not implement Comparable. Thus, it chooses the more general "inherited" version <E> print(Iterable<E>) over the restricted version <E extends Comparable<? super E>> print(Iterable<E>). Nevertheless, per point #2 above, the bytecode it produces still reads "void SortedPrinter.print(Iterable)."

The VM sees "void SortedPrinter.print(Iterable)" and begins its search for void print(Iterable) in SortedPrinter. SortedPrinter has exactly such a method. The fact that it requires its input to be Comparable is unavailable at runtime. The VM chooses that method.

Thus, all three "SortedPrinter.print()" calls end up really calling SortedPrinter.print(), even though the compiler/IDE believes it is compiling a call to Printer.print() in the HashSet case. At runtime, SortedPrinter.print() implicitly casts each element of its input to Comparable ("for (E e : input)"), and the failure to cast HashSet gives us a ClassCastException.

More Fun

This is a hole in the type system, but the fun doesn't stop there. By adding method X, I can cause a call to method Y to become a call to method Z:

import java.util.HashSet;
import static java.util.Collections.singleton;

class Base {
  static <E> Object count(Iterable<E> input) {
    System.out.println("Base");
    int count = 0;
    for (E e : input) {
      count++;
    }
    return count;
  }
}

class Sub extends Base {
/*  static <E> Integer count(Iterable<E> input) {
    System.out.println("Sub");
    int count = 0;
    for (E e : input) {
      count++;
    }
    return count;
  }*/
}

class Subsub extends Sub {
  static <E extends Comparable<? super E>> Integer count(Iterable<E> input) {
    System.out.println("Subsub");
    int count = 0;
    for (E e : input) {
      count++;
    }
    return count;
  }
}

class Caller2 {
  public static void main(String[] args) {
    Subsub.count(singleton(1));
    Subsub.count(singleton(null));
    Subsub.count(singleton(new HashSet<Object>()));
  }
}

With Sub.count() commented out, we get:

Subsub
Base
Base

With Sub.count() restored, we get:

Subsub
Subsub
Subsub
<the ClassCastException>

Notice that in no case is Sub.count() itself called. Its mere presence affects the behavior of the program.

Why? The existence of Sub.count() makes the compiler write "Integer Subsub.count()" instead of "Object Subsub.count()." At runtime, the former resolves to Subsub.count(); the latter, to Base.count().

If you do want Sub.count() to be called, you must change its return type to Object. Then we get:

Subsub
Sub
Sub

Changing the method's return type again changes which method is called. The first method the compiler finds when looking for an appropriate Subsub.count() is still Sub.count(), but now it writes "Object Subsub.count()" instead of "Integer Subsub.count()." At runtime, the former resolves to the appropriate method.

Lessons

First, the controversial part: When a compiler identifies a call to method X, it should produce code that calls method X, not hypothetical method Y.

Second, programmers should generally avoid "overriding" static methods, most of all when type parameters are involved. <E extends Comparable<? super E>> ImmutableSortedSet.of(E) and its superclass's <E> ImmutableSet.of(E) do not play nicely together. Expect some gymnastics in the ImmutableSortedSet code next release.