在Java

时间:2018-06-29 08:58:16

标签: java generics exception polymorphism try-catch

我从javaDeathMatch游戏中遇到了另一个具有挑战性的问题; 在下面的代码中,我们被问到下面的代码有什么问题。 如果我错了,请纠正我; 编译错误:无;在编译时,仍没有发生类型参数的擦除,也没有进行动态绑定,因此传递给SQLException类型的方法的参数在方法'pleaseThrow'中被认为是Exception,并且(我的意思是异常(不是SQLException)强制转换为方法中的Runtime Exception,没有错误。唯一的错误是我们没有适当的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”)!!!有什么问题吗??

3 个答案:

答案 0 :(得分:0)

这是由于类型擦除引起的。在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;
}

最后清楚的是,为什么从未达到您的捕获范围。您正在尝试捕获RuntimeExceptions,但实际上抛出的是已检查的异常。然后它向上传播并最终被JVM捕获。

  

其他阅读:Oracle Tutorial on type erasure

答案 1 :(得分:0)

此代码将无法编译,因为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.该惯用语通常称为“偷偷摸摸的抛出”,它使调用者可以抛出一个已检查的异常,就像它是未检查的异常一样。对于已检查和未检查的异常之间的区别,请使用see the official tutorialthis Q&A and StackOverflow

答案 2 :(得分:0)

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

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<RuntimeException>()时,编译器不会创建任何对象。但是它会在编译过程中推断出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;
    }
}

这很好,并且捕获块称为。 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。它尝试在捕获块中找到匹配项,但没有匹配项。 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;
    }
}

这将输出“完成”。