Why is throw e
allowed, but not throw a
, in the following case?
void test() {
try {
System.out.println();
} catch (Error | RuntimeException e) {
var a = e;
//throw a; unreported exception Throwable; must be caught or declared to be thrown
throw e;
}
}
All of this intuitively makes sense, but from JLS 14.20
A multi-catch clause can be thought of as a sequence of uni-catch clauses. That is, a catch clause where the type of the exception parameter is denoted as a union
D1|D2|...|Dn
is equivalent to a sequence of n catch clauses where the types of the exception parameters are class typesD1, D2, ..., Dn
respectively. In the Block of each of the n catch clauses, the declared type of the exception parameter islub(D1, D2, ..., Dn)
.
Since lub(Error,RuntimeException)
is Throwable
, the code above should be equivalent to:
try {
System.out.println();
} catch (Error e) {
Throwable lub = e;
throw lub;
} catch (RuntimeException e) {
Throwable lub = e;
throw lub;
}
(which obviously doesn't compile)
Moreover, the type of the a
is the type of e
"when treated as if it did not appear in an assignment context" (JLS 14.4.1), but as shown above it's not the same as the type of e
.
Is there anything I've overlooked?
Edit: this is not a duplicate of Why is it legal to re-throw a Throwable in certain cases, without declaring it? because this question is specific to multi-catch (which isn't discussed there) and arises due to a misunderstanding of a specific fragment of the JLS that addresses multi-catch. The answers provided in this question helped me to connect the dots :)
This is described in 11.2.2. This basically boils down to "exception parameters in catch (...)
are a special case".
Our goal is to show throw e;
compiles. We start with this line in 11.2.3:
It is a compile-time error if a method or constructor body can throw some exception class E when E is a checked exception class and E is not a subclass of some class declared in the throws clause of the method or constructor.
So we want to show that the try
statement in test
cannot throw Throwable
. Note that "can throw" in this context is a rigorously defined term. According to 11.2.2,
A try statement can throw an exception class E iff either:
The try block can throw E, [...]
Some catch block of the try statement can throw E and either no finally block is present or the finally block can complete normally; or
A finally block is present and can throw E.
Only the second point is relevant here. Let's show that the catch
block cannot throw Throwable
. Namely, throw e;
cannot throw Throwable
.
A throw statement whose thrown expression is a final or effectively final exception parameter of a catch clause C can throw an exception class E iff:
E is an exception class that the try block of the try statement which declares C can throw; and
E is assignment compatible with any of C's catchable exception classes; and
E is not assignment compatible with any of the catchable exception classes of the catch clauses declared to the left of C in the same try statement.
throw e;
here immediately fails the first bullet point, so we can conclude that throw e;
cannot throw Throwable
.
If it were throw a;
, then the above doesn't apply, but another clause does:
A throw statement whose thrown expression has static type E and is not a final or effectively final exception parameter can throw E or any exception class that the thrown expression can throw.