Monday Jun 07, 2010

Exception transparency in Java

One of the features being considered under Project Lambda is exception transparency. While this feature is not specifically required for adding lambda expressions to the Java language, it increases the expressive power of generic libraries that use closures.

Please direct any comments on this blog entry to the lambda-dev list, rather than to this blog.

Problem statement

One of the weaknesses of generics in Java is the treatment of checked exceptions. Generics provide reasonable power at abstracting over method return types and argument types, but they do not do a very good job when abstracting over the types of checked exceptions that can be thrown by a method. The result of this has been that few libraries use generic exceptions. Instead, libraries which take callback objects tend to move towards one of two extremes, illustrated by Runnable (which throws nothing) and Callable (which throws everything):

public interface Runnable {
    public void run();
}

public interface Callable<V> {
    V call() throws Exception;
}

Both of these extremes are undesirable. With Runnable, one must go through lengths to wrap the exception in an unchecked exception or invent an alternate means of exposing the exception to the initiator (see ExecutionException in java.util.concurrent). With Callable, one must catch Exception regardless of what the block throws, which is bad in two ways: unneeded boilerplate coding when the block does not throw, and encouraging users to catch Exception rather than a more targeted exception type.

Adding closures to the language exacerbates this problem, as more libraries will want to take block-like constructs and execute them.

The core of the problem is that generic type parameters are monadic [1]; a formal type parameter E of a generic type must represent exactly one type. But throws clauses are variadic; they can contain zero or more types. So while it is possible to generify over thrown exception types, one can only do so if one is willing to commit to the adicity of the throws clause, which is effectively useless for modeling arbitrary callbacks:

// This is pretty much useless!
public interface ExceptionalCallable<V, E extends Exception> {
    V call() throws E;
}

Variadic type parameters

One solution to this problem is to extend generics to allow a restricted form of variadic type parameters, which can represent zero or more types with a common upper bound. There are few places where variable-length lists of types are permitted in the Java language; one is the throws clause of a method, and another is (or will be, being added under Project Coin) is the catch clause of a try block.

A throws type parameter is a generic type parameter that is introduced with the keyword throws; throws type parameters implicitly have an upper bound of Exception (though any upper bound that is a subtype of Throwable may be explicitly specified) and can correspond to zero or more actual types that are subtypes of the upper bound.

Here is an example of how this might look:

 interface Block<T, throws E> {
     public void invoke(T element) throws E;
 }

 interface NewCollection<T> { 
     public<throws E> forEach(Block<T, throws E> block) throws E;
 }

Here, Block is a generic interface whose type signature includes a throws type parameter E. The forEach method in NewCollection is a generic method, where the type parameter E of block is inferred from its argument, and forEach is declared to rethrow exactly the exceptions thrown by the block argument.

Note that it is perfectly possible that a generic method or class might have more than one throws type parameter:

<T, throws X, throws Y> 
T executeOne(Block<T, throws X> first, 
             Block<T, throws Y> second) 
        throws X, Y {
    if (randomEvent())
        first.invoke();
    else
        second.invoke();
}

Exception transparency

With the throws type parameter on the Block interface, we can now accurately generify over the set of exceptions thrown by the Block; with the generic forEach method, we can mirror the exception behavior of the block in forEach(). This is called exception transparency because now the exception behavior of forEach can match the exception behavior of its block argument. Exception transparency simplifies the construction of library classes that implement idioms like internal iteration of data structures, because it is common that methods that accept function-valued arguments will invoke those functions, meaning that the library method will throw a superset of the exceptions thrown by its function-valued arguments.

Details and open issues

A throws type parameter is declared in the formal parameter list of a generic class or interface or of a generic method, is introduced by the keyword throws, and optionally may have an explicit upper bound (which must be a subtype of Throwable). If the upper bound is omitted, it is assumed to be Exception.

A throws type parameter may appear in the actual parameter list for a generic variable declaration (only in a position corresponding to a throws parameter), in a throws clause of a method, or in the formal argument of a catch block. Where it appears in the catch block, it is treated by the compiler as a catch of its erasure.

It is expected that most of the time, throws parameters will be inferred by the compiler rather than explicitly stated, such as when SAM-converting a lambda expression to a SAM interface type such as Block in our example.

A syntax is required to indicate that an actual type parameter for a throws type variable is niladic. In BGGA this was "throws Nothing"; we will likely use "throws void" or "throws Void".

We may wish to not require the throws keyword at the use site; Block<E> instead of Block<throws E>.



[1] Monadic in the sense of fixed adicity (arity), not in the sense of category theory or functional programming.

About

Brian Goetz is Java Language Architect at Oracle Corporation.

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today