我从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”)!!!有什么问题吗??
答案 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捕获。
答案 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 tutorial或this 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子句,但是代码可以很好地编译。
由于ClassCastException
是RuntimeException
,因此编译器可以很好地编译代码。 RuntimeException
是ClassCastException
的父项。
但是,当编译器将代码移交给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;
}
}
这将输出“完成”。