需要 Java 的 "more precise rethrow in exceptions"

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

我无法理解 Java 7 及更高版本中精确重新抛出的工作原理。正如 https://www.theserverside.com/tutorial/OCPJP-Use-more-precise-rethrow-in-exceptions-Objective-Java-7 中所指出的,在 Java 7 及更高版本中,我们可以在方法声明中使用 throws 子句,并使用逗号分隔的方法可能抛出的特定异常列表.如果所有这些异常都是一般异常 java.lang.Exception 的子类型,我们将能够在捕获该超类型的 catch 块中捕获它们中的任何一个,同时让客户端代码(例如调用方方法)知道实际发生了可能的子类型异常。

最初,我认为为了让客户端代码知道实际发生了哪些异常,我们需要在throws 子句中指定特定异常的列表。然而,在下面的示例中,客户端代码(main() 方法)似乎能够检索该信息,即使我们仅在被调用方法的 throws 子句中指定异常 java.lang.Exception 也是如此。因此,我的问题是:

为什么下面的代码输出相同,不管方法runException()throws子句是throws ExceptionA, ExceptionB还是throws Exception

我在 Eclipse 中使用 Oracle JVM-12。提前致谢!

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();
        }
    }
}

输出:

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

这些抛出声明是为了让您更明确地列出方法中发生的事情。否则这是普通的多态性:你使用 base class 来组合多个 subclasses,但是你绝对没有改变实例,这就是为什么在运行时在这两种情况下异常都被解析为它们的具体classes.

通常,您永远不应该 catch (Exception ex)。因为这也会捕获 RuntimeExceptions。有时捕获 (Throwable t) 或使用 Thread.setDefaultUncaughtExceptionHandler 自定义未捕获的异常处理程序以捕获异常然后将它们显示给用户是有意义的。有时我会捕获异常,将其包装在 RuntimeException(或错误)中并抛出

当涉及到异常时,您实际上应该只在可以对它们执行某些操作时捕获它们,或者当您想要确保异常不会导致方法的其余部分无法处理时。

我个人把异常分为3种

  1. 您的代码中的问题:这是您需要解决的问题
  2. 用户问题:例如,如果您告诉他们输入一个数字,而他们输入 'a',那是用户的错误
  3. "Friend"异常:例如,SocketException就是一个例子。如果套接字关闭并且您有一个线程在其上等待输入,它将抛出此异常,释放线程并让您在套接字上进行清理。

您缺少的是您需要以不同方式处理这些可能的异常的情况。您的代码正在捕获个别异常,但粗略地说,它执行的是相同的操作。

如果您处理 ExceptionA 的方式与您处理 ExceptionB 的方式有很大不同,那么抓住广泛的 Exception 将不允许您专门这样做:

catch(Exception e){
    // something unexpected happened
    // e could be an ExceptionA problem
    // e could be an ExceptionB problem
    // e could be any other unchecked exception
}

当进入 catch(Exception e){} 块时,异常几乎可以是任何东西,但您只有一个通用代码块来处理它。

除此之外,如果您调用的方法声明了特定的 checked 异常,则编译器可以帮助您仅处理这些异常,从而增加代码的可预测性

try{
    runException(ch);
} catch(ExceptionA e){
    // code specific to handling ExceptionA problems
} catch(ExceptionB e){
    // code specific to handling ExceptionB problems

} catch(ExceptionC e){ //will not compile, because not declared by runException
    // code specific to handling ExceptionB problems
}

引用@Carlos Heuberger,无论方法 runException()throws 子句是 throws ExceptionA, ExceptionB 还是 throws Exception,我的代码输出相同,因为:

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

用于引用异常对象的任何异常引用类型(在本例中为 ExceptionAExceptionBExceptionthrown通过方法 runException(),这种方法将 抛出 个类型为 ExceptionAExceptionB 的对象。这些对象的赋值将与 main() 方法的前两个 catch 的 catch 参数兼容。

在 Java 语言规范的 8.4.6, 11.2.3 and 14.20.1 段之后,我了解到我们在方法签名的 throws 子句中实际指定的是异常引用类型的列表将赋值与从方法抛出的任何可能的异常对象兼容(给定一个 class 引用类型,我们可以让它指向它自己的实例对象或者它的子classes的实例对象,而不是 superclasses)。这告诉任何其他调用方方法在使用 throws 子句调用该方法时可能必须处理哪些异常。在我的代码示例中,使用子句 throws ExceptionA, ExceptionB 的优点是我不需要在 main() 中捕获 java.lang.Exception。事实上,如果我在方法 runException() 中选择子句 throws Exception 并从 main() 中删除 cath(Exception) 块,我将得到一个编译时错误。这是因为即使我们将在 运行 时抛出 ExceptionAExceptionB 对象,编译器也会理解方法 runException() 可能抛出 runException() 类型的异常对象 Exception,它不会与 main() 中的任何 catch 参数兼容(ExceptionExceptionA 和 [=15= 的超 class ]).

那是因为,您一直在 classes 处投掷 Sub

try{
        if(what == 'A') 
            throw new ExceptionA();
        else if (what == 'B')
            throw new ExceptionB();
    }

的“异常 class” 依次被抛出,

catch(Exception e){
        throw e;
    }

在被分配到“Exception class(at Exception e)”之后,如果你指定抛出一个Superclass type throws objectReference

public static void runException(char what) throws Exception){

或 Subclass 类型 throws objectReferences at

public static void runException(char what) throws ExceptionA, ExceptionB){
  • As java 编译器允许你指定一个 throws ObjectReference,如果它是一个 Superclass 对象,则在 try 语句中被抛出.