Unreachable code compiling without error - How?
From my understanding following code should not compile as the statement "I am unreachable" is after the return
.
However, When I ran this code it is compiling absolutely fine.
Also from the JLS:Unreachable Statements it should not compile.
from the spec, at 14.21 Unreachable Statements:
A try statement can complete normally if both of the following are true:
The try block can complete normally or any catch block can complete normally.
If the try statement has a finally block, then the finally block can complete normally.
Here the try block can't complete normally but the catch block can as well as the finally block, so I am confused here
public class Test1 {
public static void main(String[] args) {
try {
return;
} catch (Exception e) {
System.out.println("catch");
} finally {
System.out.println("finally");
}
System.out.println("I am unreachable??!!!");
}
}
Can someone help me understand this behavior?
I believe these are the relevant quotes from JLS 14.21:
An empty block that is not a switch block can complete normally iff it is reachable.
A non-empty block that is not a switch block can complete normally iff the last statement in it can complete normally.
The first statement in a non-empty block that is not a switch block is reachable iff the block is reachable.
Every other statement S in a non-empty block that is not a switch block is reachable iff the statement preceding S can complete normally.
So your
System.out.println("I am unreachable??!!!");
statement is reachable iff (that means "if and only if") the try statement can complete normally, which leads to the next quote:
A try statement can complete normally iff both of the following are true:
The try block can complete normally or any catch block can complete normally.
If the try statement has a finally block, then the finally block can complete normally.
Since your catch
block can complete normally and you have a finally
block that can complete normally, the try
statement can complete normally. Hence the System.out.println("I am unreachable??!!!");
statement following it is deemed reachable, regardless of the return;
statement inside the try
block.
Note the or
in
The try block can complete normally or any catch block can complete normally.
This requires either the try
block or at least one of the catch
blocks to complete normally. It doesn't require both the try
block and catch
block to complete normally.
Finally, the logic behind this behavior:
The compiler is not supposed to analyze whether a try block can or cannot throw an Exception
. The reason is that the Exception
class hierarchy includes both checked and unchecked exceptions, and unchecked exceptions are not declared in throws
clauses (if you replaced Exception
with some checked exception, such as IOException
, the compiler would complain that your try block never throws that exception, which would make the catch
block unreachable).
Therefore, since you have a catch (Exception e)
block which can complete normally, the compiler assumes that this catch block it reachable, and therefore the entire try statement can complete normally, even though the try
block cannot complete normally.
The finally block, if present, must also be able to complete normally, since the finally
block is also executed, so if it couldn't complete normally, the entire try statement couldn't complete normally.
You have return in try.
What if there is an exception and it directly goes to catch. Hence it is not unreachable in terms of compiler and is compiling successfully.
Compilation will fail if you will have return in catch as well
Also, as per JLS 14.21:
A reachable break statement exits a statement if, within the break target, either there are no try statements whose try blocks contain the break statement, or there are try statements whose try blocks contain the break statement and all finally clauses of those try statements can complete normally.
See output below when you have return in both try and catch:
jshell> public class Test1 {
...> public static void main(String[] args) {
...> try {
...> return;
...>
...> } catch (Exception e) {
...> return;
...>
...> }
...>
...> System.out.println("I am unreachable??!!!");
...> }
...> }
| Error:
| unreachable statement
| System.out.println("I am unreachable??!!!");
| ^------------------------------------------^
Similar will be the case when you have return in your finally statement and compilation will fail.
A statement post try will be considered as reachable if :
1) Try has a return statement with catch and finally not having return statement
2) Try does not have a return statement with catch having or not having return statement and finally not having return statement
3) Try, catch and finally not having return statement
Trying to give a more simplified reason for the problem, the code is reachable, in case an exception occurs in the try block. In that case, the control further goes to catch block and then the finally block. After finally block, the particular statement will be executed.
try {
return; //line 1
} catch (Exception e) {
System.out.println("catch"); //line 2
} finally {
System.out.println("finally"); //line 3
}
System.out.println("I am unreachable??!!"); //line 4
That means, there are 2 case, hence 2 flows:
- line 1 -> line 3 -> return (In case there is no exception)
- line 1 (exception occurs) -> line 2 -> line 3 -> line 4 (In case try gets an exception)
The line will become unreachable, only if we do not leave any possibility in which the control goes there. There are 2 ways for that:
- return from catch block
- return from finally block.
In both the cases, the control can never flow to that line.
try {
return; //line 1
} catch (Exception e) {
System.out.println("catch"); //line 2
return; //return control
} finally {
System.out.println("finally"); //line 3
return; //or return from here
}
System.out.println("I am unreachable??!!"); //line 4
I hope now it gives a clear picture of the actual reason of the issue.
When you look at "unreachable statements" in a Java program, what counts is what the definition in the language says, not what a clever compiler could find out.
According to the Java Language, the last println is not an unreachable statement. Even though by looking at the code it is easy (for a clever human) to figure out that it can never be executed.
A programming language needs to rely on fixed rules that are easy for the compiler to follow exactly. A compiler cannot rely on cleverness, because different compilers would have different amounts of cleverness, so if they didn't follow the simple, fixed rules, then some compilers would find the statement unreachable, and some wouldn't.