Improper use of Generics could breach type safety

I was going through Java discussion forums and happened to come across an interesting discussion related to Generics. It was about a loop hole in the Java Generics feature that allows a method to throw a Checked Exception without declaring it in the throws clause.

This is definitely going to be freaky and I wouldn’t encourage you to take advantage of this in your development. Initially I thought this could be a loop hole in the Compiler implementation, but later realized that the behavior is inherent with the Generics design.

public class RethrowCheckedException {
    public static void rethrow(Throwable t) {//No throws clause
        RethrowCheckedException.<RuntimeException>throwing(t);
    }

    public static <T extends Throwable> void throwing(Throwable t) throws T {
        throw (T)t;
    }
    public static void main(String[] args) {
        try {
            throw new Exception("dummy");
        } catch (Exception e) {
            rethrow(e);
        }
    }
}

Using the rethrow() method we can make java.lang.Exception to escape from main() without declaring it in its throws clause. The type <RuntimeException> specified while invoking the throwing() method is know as “explicit type argument”. (which is complementary to the type inference discussed below)

Type Inference

To understand how the above thing works, we need to get an idea about type inference and explicit typing. Take a look at the the following example

public class ArrayStore {
    public static <T> void store(T[] array, T item) {
        if (array.length > 0)
            array[0] = item;
    }
    public static void main(String[] args) {
        store(new String[1], "abc"); //ok
        store(new String[1], new Integer(10)); //??
    }
}

Surprisingly, both the store method invocations shown, compile. Compiler infers T to String for the first store() invocation as both the arguments are Strings, this process is known as “type inference”. We expect the second one to fail as T cannot be evaluated to both a String as well as an Integer. But, the Compiler infers T to a common super class or interface other than java.lang.Object. As String and Integer have Serializable and Comparable as super interfaces, Compiler deduces T to be a Serializable or Comparable and allows compilation. The result of which is an ArrayStoreException at runtime, defying the whole point of Generics and type safety.

How do we resolve this problem? We can use the “explicit type argument” in such cases.


ArrayStore.<String>store(new String[1], new Integer(10)); //compilation Error

Explicitly specifying the type argument as String, forces the compiler to evaluate T to String (overriding the compiler’s type inference mechanism) and hence compilation fails.

Now that we got a hang on type inference, we’ll get back to the loop hole.

public static <T extends Throwable> void throwing(Throwable t) throws T {
    throw (T)t;
}

The throwing() method defines type <T extends Throwable> which is declared in its throws clause and is no way related to the argument passed. The rethrow() method explicitly specifies the type as RuntimeException, making the method signature of the throwing() method to appear as shown below, at compile time.

public static void throwing(Throwable t) throws RuntimeException {
    throw (RuntimeException)t;
}

Compiler evaluates the method signature by replacing the type variable T with its inferred or explicitly specified type, which is RuntimeException in our case. As a result of this Compiler doesn’t enforce exception handling or re-throwing it in the rethrow() method. After compilation and type erasure, all the generic type information is erased as shown below.

public static void throwing(Throwable t) throws Throwable {
    throw (Throwable)t;
}

Some thoughts
Type inference or explicit type argument could do some damage when the Generic type information is not captured properly. In the loop hole case, since the exception argument passed to the throwing() method is not related to the Generic type (T), compiler was blind folded. If we modify the method signature as shown below, method invocation fails to compile.

public static <T extends Throwable> void throwing(T t) throws T {
    throw (T)t;
}

Similarly, in the ArrayStore example we cannot expect the programmer to invoke the store method by explicitly specifying the type everytime at the calling context. A better way of capturing the generic type information in this case is to establish a relationship between Type variables(T’s), to enforce expected compile time checks.

public static <T, S extends T> void store(T[] array, S item) { ... }

And interestingly, the following generic type declarations work as expected unlike the ArrayStore example.

public static <T> setMap(Map<T, T> map) { ... }
public static <T> addToList(List<T> list, T element) { ...}
addToList(new ArrayList<String>(), new Integer(10)); //fails to compile

Java Language Specification(JLS) 3.0, 15.12.2.7 states “The process of type inference is inherently complex.”

Yes indeed it is really complex to understand type inference. All this appeals for better understanding of the type inference mechanism and how it applies. Probably you can get more information from FAQ by Angelika Langer.


Technorati : , ,
Del.icio.us : ,