56

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?

T-Bag
  • 10,916
  • 3
  • 54
  • 118

4 Answers4

71

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.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • 7
    One thing I'd add--`InterruptedException`. – chrylis -cautiouslyoptimistic- Jul 25 '18 at 08:24
  • 13
    @chrylis what would you add about `InterruptedException`? – Eran Jul 25 '18 at 08:39
  • 45
    I think chrylis was getting there, but he was interrupted. – Mast Jul 25 '18 at 13:16
  • 1
    @Mast Your argument was null and exceptionally illegal. – TheRealChx101 Jul 25 '18 at 15:33
  • 3
    Back when `Thread.stop(Throwable)` was implemented (other than throwing an exception at the caller), even a `return` statement could fail with an exception. – Holger Jul 25 '18 at 16:02
  • Basically what @Holger said: It's theoretically possible for the block `{ return }` to complete abnormally if interrupted. – chrylis -cautiouslyoptimistic- Jul 25 '18 at 17:29
  • 3
    @chrylis in Java, the term *interruption* is used for the signalling via `Thread.interrupt()`, which only ends blocking operations (like `wait()`) with an `InterruptedException` or gets detected when explicitly queried, i.e. via `Thread.interrupted()`. In contrast, stopping via `Thread.stop(…)` may forcibly cause exceptions at arbitrary code locations (including those trying to handle such an exception), which is why it is deprecated for a long time now and even unsupported with recent JVMs for other throwables than `ThreadDeath`, which is not a subclass of `Exception`. – Holger Jul 25 '18 at 17:54
  • @Holger I had thought that `Thread#interrupt()` could be more invasive; perhaps I need to re-read. (I'm glad not to have to deal directly with thread management in modern applications, though!) – chrylis -cautiouslyoptimistic- Jul 25 '18 at 18:11
  • 2
    @chrylis it's not, all it does it sets a *flag*, nothing more. how that flag is "consumed" is a different story – Eugene Jul 25 '18 at 19:34
15

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
Aman Chhabra
  • 3,824
  • 1
  • 23
  • 39
10

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:

  1. line 1 -> line 3 -> return (In case there is no exception)
  2. 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:

  1. return from catch block
  2. 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.

Yashi Srivastava
  • 187
  • 3
  • 13
  • While it does have value in the present case in showing ways to make the last line unreachable, to my knowledge a `return` statement in a `finally` block is a code smell. It's better avoided. – KevinLH Jul 25 '18 at 07:42
  • @KevinLH can u please tell me a scenario how a return in finally is a bad code, as I can't think of any. This is needed just as an information. – Yashi Srivastava Jul 25 '18 at 08:14
  • @KevinLH I agree with your point, But also in favor of yashi as the code was written down to demonstrate the use case and give the better understanding of unreachable code use case – Ravi Jul 25 '18 at 08:21
  • 7
    @YashiSrivastava Because `return` in a `finally` block will override any return in the catch block, and it also swallows exceptions. See [this question](https://stackoverflow.com/questions/48088/returning-from-a-finally-block-in-java) and its answers. – MC Emperor Jul 25 '18 at 08:51
  • 3
    Also, it can replace the value that is returned **after** a `return`-statement in the `try`-block (or `catch`-block) has been successfully evaluated - that can lead to some headache: [see this question](https://stackoverflow.com/questions/2309964/multiple-returns-which-one-sets-the-final-return-value) – Hulk Jul 25 '18 at 08:57
  • 1
    @YashiSrivastava and because in general, having more than one return statement in a method is considered a code smell, no matter where they are located. – jwenting Jul 25 '18 at 12:09
  • 3
    @jwenting: By SESE purists and cargo cultists, maybe. But not "in general". If you know what to return, then returning is the most appropriate thing to do. (There are good reasons for not returning within a `finally` block, but they have less to do with SESE and more with making the code lie.) – cHao Jul 25 '18 at 17:07
4

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.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • This would be a clearer answer if you said WHY the last line is reachable (your second paragraph, but not the other 2 paragraphs). But in any case the answer is wrong! Because the last line of code IS reachable, (if an exception is ever caught). – andrewf Jul 25 '18 at 19:19
  • @andrewf well, the example in the question (a block containing nothing but a single `return;` statement) cannot throw as far as I know (in a modern runtime that does not implement `Thread.stop` - [see also Holger's comment](https://stackoverflow.com/questions/51511249/unreachable-code-working-fine-how#comment90015402_51511465)) – Hulk Jul 26 '18 at 07:22
  • @Hulk, fair point! (I guess I was reading that line as stand-in code for something which DID do something that could throw, but of course you’re right.) – andrewf Aug 05 '18 at 16:01