简体   繁体   中英

Java unhandled exception disappears

Suppose there is a class such that:

public class Magic {

public static void main(String[] args){
    boolean[] val = new boolean[0];
    paradox(val);
}

private static boolean paradox(boolean[] arg) {
    Container container = null;
    try {
        container = Container.getContainer(arg[0]);
        return container.value;
    } catch (UnsupportedOperationException e) {
        e.printStackTrace();
        return false;
    } finally {
        try {
            container.sayHi();
        } catch (UnsupportedOperationException e) {
            e.printStackTrace();
        }
    }
}

private static class Container{
    private boolean value;

    private Container(boolean value){
        this.value = value;
    }

    private static Container getContainer(boolean value) throws UnsupportedOperationException{
        return new Container(value);
    }

    private void sayHi(){
        System.out.println("Hi!");
    }
}
}

If this code is executed, there is a null pointer thrown on line with

container.sayHi();

container should, in fact, be null. Before the assignment can complete there is an ArrayIndexOutOfBoundException thrown when we call getContainer(). However, what happens to the ArrayIndexOutOfBoundException? Why do we go into finally{} after an unhandled exception?

edit: poor phrasing. question is why we go directly into finally{}. And what happens to ArrayIndexOutOfBoundException

Why do we go into finally{} after an unhandled exception?

We always go to finally after a block exits (successfully, after an exception handler, or after an unhandled exception). That's exactly what finally is for: a place to put code that will be run no matter what.

However, what happens to the ArrayIndexOutOfBoundException?

If you encounter a second exception in an exception handler or a finally block, then that second exception will be propagated and the original exception will be hidden.

If you want to preserve the original exception, you can manually attach it to the new exception via Throwable#addSuppressed (or the other way around, re-throw the original exception and attach the new one as suppressed).

There is a simple rule in Java: finally is always called. 1

So what happens is this:

container = Container.getContainer(arg[0]);

This throws an ArrayIndexOutOfBoundException , which is uncaught. Before the exception bubbles, finally is called.

container.sayHi();

container == null so a NullPointerException is thrown, this shadows the original exception. As per the JLS §14.20.2

If execution of the try block completes abruptly because of a throw of a value V, then there is a choice

... then

If the finally block completes abruptly for reason S, then the try statement completes abruptly for reason S (and the throw of value V is discarded and forgotten ).

emphasis mine

1: except when System.exit is called or some other rare cirumstances.

Another surprise you may get when doing control flow operations inside finally block :

public static void main(String[] args) {
    System.out.println(badFunction());
}

private static String badFunction() {
    try {
        throw new RuntimeException("Catch it!");
    } finally {
        return "Exception disappears";
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM