【问题标题】:throwing Generic Exception in java在java中抛出通用异常
【发布时间】:2018-06-29 08:58:15
【问题描述】:

我还有一个来自 javaDeathMatch 游戏的具有挑战性的问题; 在下面的代码中,我们被问到下面的代码有什么样的问题。 如果我错了,请纠正我; 编译错误:无;在编译时,类型参数的擦除还没有发生,动态绑定还没有发生,所以传递给 SQLException 类型的方法的参数在方法“pleaseThrow”中被认为是异常,它(我的意思是Exception not SQLException) 在方法中被强制转换为运行时异常,没有错误。唯一的错误是我们没有适当的 catch 子句来捕获异常。

public class Exption<T extends Exception> {
    public static void main(String[] args) {
        try {
            new Exption<RuntimeException>().pleaseThrow(new SQLException());
        }catch (final SQLException ex){
            ex.printStackTrace();
        }
    }
    private void pleaseThrow(final Exception t) throws T{
        throw (T)t;
    }
}

如果我们用这个替换 catch 子句:

catch(final RuntimeException e){
    e.printStackTrace();
    System.err.println("caught");
}

异常会被捕获,但 System.err.println("caught") 永远不会被打印!!!什么问题???

【问题讨论】:

    标签: java generics exception polymorphism try-catch


    【解决方案1】:

    这是由于类型擦除造成的。在编译后的java中,每个通用信息都会丢失(还有一些东西,但这与此无关)。这意味着在编译期间,泛型变量T 等于RuntimeException。所以您的 pleaseThrow 代码如下所示:

    private void pleaseThrow(final Exception t) throws RuntimeException{
        throw (RuntimeException)t;
    }
    

    不过,在编译之后,每个泛型参数都会被擦除为基本类型。在您的情况下,发送至Exception。这给你留下了这样的方法签名:

    private void pleaseThrow(final Exception t) throws Exception{
        throw (Exception)t;
    }
    

    最后很清楚,为什么你的 catch 块永远不会到达。你试图捕捉RuntimeExceptions 但你实际上抛出的是一个检查异常。然后向上传播,最终被JVM捕获。

    补充阅读:Oracle Tutorial on type erasure

    【讨论】:

      【解决方案2】:

      此代码将无法编译,因为SQLException 是一个已检查异常,并且要捕获已检查异常,必须将其声明为由try 块内的某些内容引发。 Here it is failing to compile on Ideone,例如,带有以下消息:

      Main.java:7: error: exception SQLException is never thrown in body of corresponding try statement
              }catch (final SQLException ex){
               ^
      Note: Main.java uses unchecked or unsafe operations.
      Note: Recompile with -Xlint:unchecked for details.
      

      如果您更改 catch 块使其捕获RuntimeException,那么代码将编译,但异常将不会被捕获,因为SQLException 不是RuntimeException 的子类。

      There's a discussion of how the pleaseThrow method works here. 这个习惯用法通常被称为“sneaky throws”,它让调用者抛出一个检查异常,就好像它是一个未经检查的异常一样。对于已检查和未检查异常的区别,see the official tutorialthis Q&A and StackOverflow

      【讨论】:

        【解决方案3】:

        让我们采用您的代码的第一个版本:

        public class Exption<T extends Exception> {
            public static void main(String[] args) {
                try {
                    new Exption<RuntimeException>().pleaseThrow(new SQLException());
                }catch (final SQLException ex){ // This is compilation error
                    ex.printStackTrace();
                }
            }
            private void pleaseThrow(final Exception t) throws T{
                throw (T)t;
            }
        }
        

        编译器如何检测错误?

        不是因为throw (T)t;。事实上,如果您将类型转换为类型参数,编译器会忽略它并将其留给 JVM。

        那么编译器是如何产生错误的呢?

        正因为如此:

        private void pleaseThrow(final Exception t) throws T
        

        注意 throws T 。当您说 new Exption&lt;RuntimeException&gt;() 时,编译器不会创建任何对象。但它在编译期间推断T。它在它的权力之下。

        现在,让我们全面了解为什么会产生错误。

        private void pleaseThrow(final Exception t) throws T{ 的编译器知道您将抛出 RuntimeException。

        根据类型转换的规则,编译器检查两种类型,如果一个是另一个的父级。如果是,它会将代码传递给 JVM。然后只有 JVM 会进一步检查一个对象是否可以实际类型转换为另一个对象。

        同样,编译器检查 throws 和 catch 块是否兼容。如果有多个兼容,则将决定权留给 JVM。编译器也会检查许多其他事情,但让我们专注于主轨道。

        在您的示例中,一种类型是 SqlException,另一种是 RuntimeException。没有人是其他人的父母。因此,编译器显示错误。

        让我们看几个例子来清除它:

        public class Exption<T extends Exception> {
            public static void main(String[] args) {
                try {
                    new Exption<RuntimeException>().pleaseThrow(new IllegalArgumentException());
                }catch (final ClassCastException ex){
                    ex.printStackTrace();
                    System.out.println("done");
                }
            }
            private void pleaseThrow(final Exception t) throws T{
                throw (T)t;
            }
        }
        

        在这个例子中,Catch 子句不会被调用,但是代码编译得很好。 由于 ClassCastExceptionRuntimeException ,因此编译器可以很好地编译代码。 RuntimeExceptionClassCastException 的父级。 但是当编译器将代码交给 JVM 时,JVM 知道异常对象的类型为 IllegalArgumentException,因此将满足于 IllegalArgumentException 的 catch 子句或其超类型。在这里,我们没有这样的东西。因此,不会调用 Catch 子句,因为没有匹配项。

        再举一个例子:

        public class Exption<T extends Exception> {
            public static void main(String[] args) {
                try {
                    new Exption<RuntimeException>().pleaseThrow(new IllegalArgumentException());
                }catch (final RuntimeException ex){
                    ex.printStackTrace();
                    System.out.println("done");
                }
            }
            private void pleaseThrow(final Exception t) throws T{
                throw (T)t;
            }
        }
        

        这运行良好并且调用了catch块。 JVM 知道对象类型将是IllegalArgumentException 并且 RuntimeException 是超类,因此,由于超类能够引用子类对象,所以它匹配。

        现在,让我们回到你的代码。

        您只编写了一个由SqlException 组成的catch 块,这是一个checked exception,因此不能从pleaseThrow() 抛出,因为它根据编译器抛出RuntimeException。

        因此,会生成此错误:

        Error:(9, 10) java: exception java.sql.SQLException is never thrown in body of corresponding try statement
        

        如您所知,在 Java 中使用 catch 块捕获从未抛出的已检查表达式是非法的。


        现在,让我们来看看您的代码的第 2 版:

        public class Exption<T extends Exception> {
            public static void main(String[] args) {
                try {
                    new Exption<RuntimeException>().pleaseThrow(new SQLException());
                }catch(final RuntimeException e){
                    e.printStackTrace();
                    System.err.println("caught");
                }
            }
            private void pleaseThrow(final Exception t) throws T{
                throw (T)t;
            }
        }
        

        现在,在你写的RuntimeException 的 catch 块中,事情变得有意义了。 编译器看到该方法抛出 RuntimeException 并且在 catch 块中我们也有运行时异常。

        现在,让我们仔细看看。编译器很容易编译这段代码并将其发送到 JVM。但在此之前,它会进行类型擦除。

        让我们看看类型擦除会对我们的代码造成什么影响: [在实际提供给 JVM 的代码中,是字节级代码。下面的示例显示了擦除对代码的作用。请注意,如果您尝试编译,以下代码将无法在编译器中正常运行,因为此代码是在类型擦除之后并且几乎没有丢失编译器需要的细节。然而,JVM 可以运行与此等效的字节码。]

        public class Exption {
            public static void main(String[] args) {
                try {
                    new Exption().pleaseThrow(new SQLException());
                }catch(final RuntimeException e){
                    e.printStackTrace();
                    System.err.println("caught");
                }
            }
            private void pleaseThrow(final Exception t) throws java.lang.Exception {
                throw (java.lang.Exception) t;
            }
        }
        

        要了解这段代码是如何简化为 的,您需要阅读有关类型擦除的内容。不过暂时相信我吧。

        现在,这段代码是一段非常有趣的代码。

        JVM 直接在这段代码上运行。如果你看到 JVM 知道它方法会抛出一个 SqlException 类型的 Object 类型转换为 Exception。它试图在 catch 块中找到匹配但没有匹配。 RunTimeException 不是SqlException 的超类。因此,没有调用 catch 块。

        让我们修改代码以更了解它。

        public class Exption<T extends Exception> {
            public static void main(String[] args) {
                try {
                    new Exption<RuntimeException>().pleaseThrow(new SQLException());
                }catch(final RuntimeException e){
                    e.printStackTrace();
                    System.err.println("caught");
                }catch(Exception r){
                    System.out.println("done");
                }
        
            }
            private void pleaseThrow(final Exception t) throws T{
                throw (T)t;
            }
        }
        

        这将输出“完成”。

        【讨论】:

          猜你喜欢
          • 2016-08-10
          • 1970-01-01
          • 2012-07-23
          • 1970-01-01
          • 1970-01-01
          • 2013-06-24
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多