Inferring that what exceptions are thrown from a lambda
Brian Goetz
brian.goetz at oracle.com
Sun Sep 1 09:07:54 PDT 2013
First, let's note this has nothing directly to do with lambdas; the same
thing happens with anonymous classes.
This is purely a limitation of how generics interact with exceptions
(which is to say, not so well.)
If you have:
public interface Action<T, E extends Throwable> {
T run() throws E;
}
This means: for every Action, there must be exactly one type, bounded by
Throwable, represented by E. There is no way to parameterize Action
with an E that means "just kidding, I don't throw anything."
Note also that there's no way for Action to work nicely with something
that throws more than one unrelated exception (say, IOException or
SQLException) without just taking their least-upper-bound (here,
Exception). Another restriction of the "one type variable, one type"
rule of generics. Again, nothing to do with lambda, except that lambda
exposes more cases where you'd like to do this.
There have been a number of proposals to address this weakness, under
various names (exception transparency, variadic generics) which would
let a type variable E, instead of referring to exactly one type, refer
to zero or more types. These were explored during the development of
Project Lambda and deemed to be a poor balance of complexity to power,
and so we did not move forward on any of these proposals. (Search the
list archives for the details.)
So, tl;dr version:
- A generics problem, not a lambda problem
- Not a new problem
- Possible solutions explored and rejected
On 8/31/2013 2:04 PM, Esko Luontola wrote:
> Given an interface and method like this:
>
> public interface Action<T, E extends Throwable> {
> T run() throws E;
> }
>
> public static <T, E extends Throwable> T tryRepeatedly(int
> maxTries, Action<T, E> action) throws E {
> // ...
> }
>
> If the method is provided a lambda that throws e.g. an IOException, the
> compiler figures out correctly that the method may also throw
> IOException and requires that exception to be rethrown:
>
> public void compiles() throws IOException {
> String result = Resilient.tryRepeatedly(10, () -> {
> throw new IOException("dummy exception");
> });
> }
>
> But if the lambda doesn't thrown anything, the compiler thinks that the
> method may throw the most generic exception. The following code fails to
> compile with "error: unreported exception Throwable; must be caught or
> declared to be thrown"
>
> public void doesNotCompile() {
> String result = Resilient.tryRepeatedly(10, () -> "result");
> }
>
> So the developer must declare the surrounding method to throw an
> exception that may never be thrown:
>
> public void compilesButUndesirable() throws Throwable {
> String result = Resilient.tryRepeatedly(10, () -> "result");
> }
>
> Is this as planned or is it a bug? Is there a workaround? I would expect
> that since the compiler can figure out what exception the first case
> throws, it should also figure out that the latter case doesn't throw
> checked exceptions.
>
More information about the lambda-dev
mailing list