Search code examples
javaexceptionrethrow

Need of Java's "more precise rethrow in exceptions"


I am having trouble understanding how precise rethrow works in Java 7 and later versions. As pointed out in https://www.theserverside.com/tutorial/OCPJP-Use-more-precise-rethrow-in-exceptions-Objective-Java-7, in Java 7 and later versions we can use the throws clause, in a method declaration, with a comma-separated list of specific exceptions that the method could throw. If all these exceptions are subtypes of the general exception java.lang.Exception, we will be able to catch any of them in a catch block that catches this supertype, while letting client code (eg. a caller method) to know which of the possible subtypes exceptions actually occurred.

Initially, I thought that in order to let know client code which exception actually occurred, we needed to specify the list of specific exceptions in the throws clause. Nevertheless, in the following example the client code (the main() method) seems able to retrieve that information, even if we only specify the exception java.lang.Exception in the throws clause of the called method. Therefore, my question is:

Why the following code outputs the same, regardless of whether the throws clause of the method runException() is throws ExceptionA, ExceptionB or throws Exception ?

I am using Oracle JVM-12 in Eclipse. Thanks in advance!

class ExceptionA extends Exception{}
class ExceptionB extends Exception{}

public class RethrowingAndTypeChecking{
    public static void runException(char what) throws Exception{
    //public static void runException(char what) throws ExceptionA, ExceptionB{
        try{
            if(what == 'A') 
                throw new ExceptionA();
            else if (what == 'B')
                throw new ExceptionB();
        }
        catch(Exception e){
            throw e;
        }
    }

    public static void main (String args[]){
        char ch;
        for (int i=0;i<2;i++) {
            if(i==0) ch='A';
            else ch = 'B';

            try{
                runException(ch);
            }
            catch(ExceptionA e){
                System.out.print("In main(), 'catch(ExceptionA e){}', caught exception: " + e.getClass());
            }
            catch(ExceptionB e){
                System.out.print("In main(), 'catch(ExceptionB e){}', caught exception: " + e.getClass());
            }
            catch(Exception e){
                System.out.print("In main(), 'catch(Exception e){}', caught exception: " + e.getClass());
            }               
            System.out.println();
        }
    }
}

output:

In main(), 'catch(ExceptionA e){}', caught exception: class ExceptionA
In main(), 'catch(ExceptionB e){}', caught exception: class ExceptionB

Solution

  • Quoting @Carlos Heuberger, my code outputs the same, regardless of whether the throws clause of the method runException() is throws ExceptionA, ExceptionB or throws Exception because:

    the run-time type of the exception is used to select the catch clause: see 14.20.1. Execution of try - catch

    Whatever the exception reference type (in this case ExceptionA, ExceptionB or Exception) used to refer to the exception object thrown by method runException(), such method will throw objects of type either ExceptionA or ExceptionB. These objects will be assignment compatible with the catch parameters of the first two catch of the main() method.

    After paragraphs 8.4.6, 11.2.3 and 14.20.1 of the Java Language Specification, I understood that what we actually specify in a throws clause of a method signature is the list of the exception reference types that will be assignment compatible with any possible exception object thrown from the method (given a class reference type we can make it point to instance objects of itself or to instance objects of its subclasses, not superclasses ). That tells any other caller method what exceptions it may have to deal with when invoking the method with the throws clause. In my code example, the advantage of using the clause throws ExceptionA, ExceptionB is that I will not need to catch java.lang.Exception in the main(). In fact, if I choose clause throws Exception in method runException() and delete the cath(Exception) block from the main() I will get a compile-time error. This is because even if we will be throwing ExceptionA or ExceptionB objects at run-time, the compiler will understand that method runException() may throw out an exception object of type Exception, which will not be assignment compatible with any of the catch parameters in the main() (Exception is a superclass of both ExceptionA and ExceptionB).