Consider the below code snippets
In code snippet 1, method m1() has SQLException in throws declaration, but it is actually throwing a reference variable of type Exception
. I was expecting compiler error here, since Exception
is not mentioned in the throws
declaration. But it compiles and prints Caught successfully
import java.sql.SQLException;
public class Snippet1{
private static void m() throws SQLException{
try{
throw new SQLException();
} catch(Exception e){
throw e;
}
}
public static void main(String[] args){
try{
m();
} catch(SQLException e){
System.out.println("Caught successfully"); //prints "Caught successfully
}
}
}
Code snippet 2 is almost same as the previous one, except that we assigned null
to the exception reference variable e
and then throw it. Now the compiler complains that Exception
must be caught or declared to be thrown.
import java.sql.SQLException;
public class Snippet2{
private static void m() throws SQLException{
try{
throw new SQLException();
} catch(Exception e){
e = null;
throw e;
}
}
public static void main(String[] args){
try{
m();
} catch(SQLException e){
System.out.println("Caught successfully");
}
}
}
I do not understand why #1 compiles and #2 doesn't.
CodePudding user response:
This is described in JDK 7 Rethrowing Exceptions with More Inclusive Type Checking
public void rethrowException(String exceptionName) throws FirstException, SecondException { try { // ... } catch (Exception e) { throw e; } }
The Java SE 7 compiler can determine that the exception thrown by the statement throw e must have come from the try block, and the only exceptions thrown by the try block can be
FirstException
andSecondException
. Even though the exception parameter of the catch clause, e, is typeException
, the compiler can determine that it is an instance of eitherFirstException
orSecondException
This analysis is disabled if the catch parameter is assigned to another value in the catch block. However, if the catch parameter is assigned to another value, you must specify the exception type
Exception
in the throws clause of the method declaration.
CodePudding user response:
There's a rather special voodoo magic rule in the JLS, introduced sometime after JDK6 (I think in JDK7, together with 'multi-catch', where you name multiple exception types, separated with the bar (|
) character).
If you catch an 'overly broad' exception type, and the variable is final
or 'effectively final' (never re-assigned), then any throw t;
statement where t
is that variable is treated as only representing the exception type(s) that the associated try
body could actually throw.
In other words:
- In the first snippet, your
Exception e
is effectively final, therefore the rule kicks in. - Given that the rule kicks in,
throw e;
in snippet 1 is treated as ife
is actually tightened up to be a disjoint type of all things the try body can throw that are typed asException
or some subtype thereof. In this case, that means: JustSQLException
. The method body is declared tothrows SQLException
, thus,throw e;
is acceptable. - In the second snippet,
e
is no longer effectively final, as it is re-assigned. Therefore this rule does not kick in, andthrow e;
is simply interpreted as an attempt to throwException
, which isn't legal as the method body doesn'tthrows
it and the throw statement is not in atry
block whosecatch
block deals withException
.
This feature was added to make it simpler to write code that wants to 'peek' at thrown exceptions - they want to do something when an exception occurs, but then they want the exception handling to continue as if they didn't, i.e. that the exception is still thrown, completely unchanged. That's more or less what finally
is for, except finally
runs in all cases: All exception types, and also runs even if the try body finishes normally or control flows out.
The relevant section of the official documentation is Java Language Specification §11.2.2.