Why java does not detect unreachable catch block if I use multiple catch blocks?
Asked Answered
C

3

8

Research following method:

static private void foo()  {
        try {
            throw new FileNotFoundException();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

This code compiles good despite last catch block actually unreachable.

Now lets comment throw new FileNotFoundException(); row

execute:

OOOPs! we see

Unreachable catch block for FileNotFoundException. This exception is never thrown from the try statement body

Strange. Why does java use double standards for these situatons?

update for @Peter Rader

static private void foo(FileNotFoundException f)  {
        try {
            throw f;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

work as well as with constructor invocation

update

I noticed that on different versions of java compiler I see different result of compiling this code.

public class RethowTest {

        public static void main(String[] args)  {
            try {
                throw new FileNotFoundException();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                throw e;
            }
        }    
}

on my local pc: java 1.7.0_45 -

C:\Program Files\Java\jdk1.7.0_45\bin>javac D:\DNN-Project\DNN-Project\src\main\java\exceptionsAndAssertions\RethowTest.java
D:\DNN-Project\DNN-Project\src\main\java\exceptionsAndAssertions\RethowTest.java:15: warning: unreachable catch clause
                } catch (IOException e) {
                  ^
  thrown type FileNotFoundException has already been caught
1 warning

java 1.6.0_38

D:\DNN-Project\DNN-Project\src\main\java\exceptionsAndAssertions\RethowTest.java:16: unreported exception java.io.IOException; must be caught or declared to be thrown
                    throw e;
                    ^
1 error

http://www.compileonline.com/compile_java_online.php (Javac 1.7.0_09) -

HelloWorld.java:9: warning: unreachable catch clause
        } catch (IOException e) {
          ^
  thrown type FileNotFoundException has already been caught
1 warning
Cressler answered 2/9, 2014 at 13:14 Comment(16)
Exception includes runtime exceptions. It's never unreachable in principle. A catch for FileNotFoundException is only reachable if something in the try block throws it, or one of its base classes.Infinitesimal
@EJP For gods sake, can you please add it as an answer ?Fiorenza
@sᴜʀᴇsʜ ᴀᴛᴛᴀ EJP comment is not answer - it is just observation to improve questionCressler
The only not very satisfying excuse is that FileNotFoundException is an IOException too, and the catch only seems to check, that an IOException is thrown, not considering it being already catched.Jaehne
@Joop Eggen interesting opinionCressler
@Cressler If FileNotFoundException would have been catched after catching IOException the compiler gives an error, so my guess is that not all use cases were considered. And one has to admit that unreachable-code errors are not functional errors causing the application to fail.Jaehne
I suspect that reason may be similar to why if(flase){dead code} is allowed when while(false){dead code} is not - it can help in simple debugging.Dodgson
@Dodgson didn't know about while(false). And what the reason?Cressler
@Cressler It is only a guess which I read somewhere (not on official forum), but here I go: if(flase){dead code} can help in simple debugging (for instance performed by novice programmers) so they could write if (false){..} if(true){..} if(false){..} if(true){..} and by change some false to true they could test few combinations of some scenarios. But while(false) is not that useful because even if we change false to true we will end up in infinite loop, so we will not to be able to leave and test other combinations. I am not the author of this idea but it seems reasonable.Dodgson
@Cressler This answer seems to agree with this kind of rationale, also if T.J. Crowder agrees then I believe him.Dodgson
@Cressler From JLS 14.21 *the rationale for this differing treatment is to allow programmers to define "flag variables" such as: static final boolean DEBUG = false; and then write code such as: if (DEBUG) { x=3; }. So maybe for similar reasons, we compiler allows adding code for supertype exceptions even if all exceptions ware handled (for example to do catch(Exception e){//some kind of logging when we are sure exceptions shouldn't happen in tested case}).Dodgson
I have read this answer but I didn't understand. Can you answer in details in answer(not in comment)Cressler
This is only a guess/suspicion so it is better suited as comment. Also it would be more about if(false) vs while(false) rather than catch(SuperTypeOfHandledException e).Dodgson
I didn't understand connection between: 1. my question 2 debug 3 C language 4 binary backward compatibilityCressler
@Cressler If f is null, the constructor of NullPointerException is called and the Constructor can throw a IOException in fillInStackTrace. See community.oracle.com/thread/1445008?start=0Adalbert
Simple question: Why do you care? The Java compiler does what it wants, and effectively defines the language. The behavior you've described does not create a functional limitation, so why worry about it?Stack
M
5

The reachability rules are defined in the Java 8 JLS 14.21 (and Java 7) as follows:

A catch block C is reachable iff both of the following are true:

  • Either the type of C's parameter is an unchecked exception type or Exception or a superclass of Exception, or some expression or throw statement in the try block is reachable and can throw a checked exception whose type is assignable to the type of C's parameter. (An expression is reachable iff the innermost statement containing it is reachable.)

    See §15.6 for normal and abrupt completion of expressions.

  • There is no earlier catch block A in the try statement such that the type of C's parameter is the same as or a subclass of the type of A's parameter.

Note that the rules DO NOT forbid your example code. The second catch block does not meet the criteria of the second bullet point.

(In the original version of the example, you caught Exception. The reachability reasoning would be different, but the answer is the same - valid code.)

Is this inconsistent? For your example, you could argue that is the case.

Why didn't they address this case in the reachability rules? I don't know. You'd need to ask the Java designers!! However:

  • The formulation of the reachability rules would need to be significantly more complicated to handle this. Extra (unnecessary?) complexity in a specification is a concern.

  • You could argue that this inconsistency doesn't break anything. The reachability rules are really just a way of picking up potential errors in the users code. It doesn't involve type-safety or predictable execution; i.e. stuff that would "break" Java runtime semantics.

  • If they changed the spec now, that would render invalid a small proportion of valid and working Java programs. That's not a good idea, given that stability is one of the main selling points of Java.

On the other hand, I cannot think of a technical reason why they couldn't have addressed this "inconsistency" in the spec.


You noted that some Java compilers give a Warning message on the 2nd catch. That is OK. A Java compiler is allowed to give warnings for things that are (technically) legal Java code.

If they were Errors, that would technically be a compiler bug ... according to my reading of the JLS.

Milliemillieme answered 2/9, 2014 at 22:52 Comment(2)
The second catch block does not meet the criteria of the second bullet point. can you clarify?why not?Cressler
@Cressler The second bullet point means you can't catch IOException followed by FileNotFoundException because FileNotFoundException is a subclass of IOException. Your example has the opposite order, so that point doesn't apply.Everard
I
1

The catch (Exception ...) block will catch runtime exceptions. It's never unreachable in principle.

FileNotFoundException is a checked exception. A catch block for it is only reachable if something in the try block throws it, or one of its child classes.

[in response to requests]

Infinitesimal answered 2/9, 2014 at 23:53 Comment(0)
A
-3

If you instanciate new FileNotFoundException() you call the constuctor of the Class FileNotFoundException. In this constructor a IOException can theroretically be thrown by calling the native method fillInStackTrace - the compiler may not know whats the content of the Constructor, maybe a IOException will be thrown.

See this article: https://community.oracle.com/thread/1445008?start=0 in example.

If the compiler looks into the constructor FileNotFoundException() for every occourcence: its a overhead java neglect for performance.

Adalbert answered 2/9, 2014 at 13:28 Comment(9)
+1 you are right. Now its weird - no doubt. But f may be null, a Null-Pointer exception will be thrown, noone knows if NullPointerException not inheried a IOException.Adalbert
I don't understan your idea. Null-Pointer is RuntimeException annyway. You shouldn't check these exceptions.Cressler
1. Dont think classes and their inheritations do not change! Even the compiler do not think inheritations change! 2. NumberFormatException is a RuntimeException too, you realy should check these exceptions!Adalbert
@PeterRader This is complete nonsense from start to finish. (1) new FileNotFoundException() does not throw any kind of IOException. (2) FileNotFoundException and NullPointerException are not in an inheritance relationship in either direction. (3) The compiler knows what every constructor and method throws, without 'looking into the constructor': it's in the object code. (4) Java checks all checked exceptions: there is no 'neglect for performance'.Infinitesimal
@EJP 1. The compiler knows only the objectcode of the current JRE/JDK-Version. In this Bug in example the native method fillInStackTrace throws a IOException who is called in the constructor of RuntimeException (the FileNotFoundException also uses) uses: community.oracle.com/thread/1445008?start=0 . 2. I can use dynamic proxys to inherit from NullPointerException as well as FileNotFoundException. (3) The compiler knows only what every constructor or method declare in their method/constructor signature. A interface-method do not have object-code, that does mean the method thAdalbert
Sorry take too much lines for discussion. If you have doubts about your point, open up a chat and let me aregue you about my point.Adalbert
@PeterRader 1. The compiler knows enough to check checked exceptions, and it knows that constructing a FileNotFoundException doesn't throw an IOException, which is more than you seem to. 2. You cannot use dynamic proxies to inherit from anything, only to implement interfaces. 3. The question of what may change in future JREs isn't the compiler's concern, but nobody is going to change the JRE in the respect you suggest. You're just confusing the issue. You haven't even answered the question properly. If everything you've said was true the compiler wouldn't be able to check exceptions at all.Infinitesimal
@PeterRader The link you provide is not an example of an IOException being thrown. It is an example of a RuntimeException being thrown. Look again.Infinitesimal
@EJP no, the IOException is thrown and wrapped and thrown into a RuntimeException by the container (BEA WebLogic). A real IOException is thrown!Adalbert

© 2022 - 2024 — McMap. All rights reserved.