Java JLS的哪些部分可以证明能够像未检查异常一样抛出已检查异常?
我知道可以通过javac编译器潜入一个检查过的异常,并将其抛出到不能抛出的地方。它在Java 6和7中编译和运行,抛出一个Java JLS的哪些部分可以证明能够像未检查异常一样抛出已检查异常?,java,exception,generics,jls,checked-exceptions,Java,Exception,Generics,Jls,Checked Exceptions,我知道可以通过javac编译器潜入一个检查过的异常,并将其抛出到不能抛出的地方。它在Java 6和7中编译和运行,抛出一个SQLException,而不抛出throws或catch子句: public class Test { // No throws clause here public static void main(String[] args) { doThrow(new SQLException()); } static void d
SQLException
,而不抛出throws
或catch
子句:
public class Test {
// No throws clause here
public static void main(String[] args) {
doThrow(new SQLException());
}
static void doThrow(Exception e) {
Test.<RuntimeException> doThrow0(e);
}
static <E extends Exception> void doThrow0(Exception e) throws E {
throw (E) e;
}
}
公共类测试{
//这里没有条款
公共静态void main(字符串[]args){
doThrow(新的SQLException());
}
静态空位点箭头(例外e){
测试doThrow0(e);
}
静态void doThrow0(异常e)抛出e{
投掷(E)E;
}
}
生成的字节码表明JVM并不真正关心已检查/未检查的异常:
// Method descriptor #22 (Ljava/lang/Exception;)V
// Stack: 1, Locals: 1
static void doThrow(java.lang.Exception e);
0 aload_0 [e]
1 invokestatic Test.doThrow0(java.lang.Exception) : void [25]
4 return
Line numbers:
[pc: 0, line: 11]
[pc: 4, line: 12]
Local variable table:
[pc: 0, pc: 5] local: e index: 0 type: java.lang.Exception
// Method descriptor #22 (Ljava/lang/Exception;)V
// Signature: <E:Ljava/lang/Exception;>(Ljava/lang/Exception;)V^TE;
// Stack: 1, Locals: 1
static void doThrow0(java.lang.Exception e) throws java.lang.Exception;
0 aload_0 [e]
1 athrow
Line numbers:
[pc: 0, line: 16]
Local variable table:
[pc: 0, pc: 2] local: e index: 0 type: java.lang.Exception
//方法描述符#22(Ljava/lang/Exception;)V
//堆栈:1,局部变量:1
静态void doThrow(java.lang.Exception e);
0 aload_0[e]
1 invokestatic Test.doThrow0(java.lang.Exception):void[25]
4返回
行号:
[个人计算机:0,第11行]
[pc:4,第12行]
局部变量表:
[pc:0,pc:5]本地:e索引:0类型:java.lang.Exception
//方法描述符#22(Ljava/lang/Exception;)V
//签名:(Ljava/lang/Exception;)V^TE;
//堆栈:1,局部变量:1
静态void doThrow0(java.lang.Exception e)抛出java.lang.Exception;
0 aload_0[e]
1阿思罗
行号:
[个人计算机:0,第16行]
局部变量表:
[pc:0,pc:2]本地:e索引:0类型:java.lang.Exception
JVM接受这一点是一回事。但是我怀疑Java语言是否应该。JLS的哪些部分证明了这种行为的合理性?是虫子吗?还是Java语言隐藏得很好的“特性”
我的感受是:
的doThrow0()
绑定到doThrow()中的
。因此,RuntimeException
在
中不需要沿着的行抛出doThrow()
子句
的赋值与RuntimeException
兼容,因此编译器不会生成强制转换(这将导致Exception
)ClassCastException
Class.newInstance()
是另一个,Thread.stop(Trowable)
JLS不接受此行为的唯一方法是运行时(JVM)将强制执行此行为
至于它是被指定的:它不是。选中和未选中异常的行为相同。选中的异常只需要catch块或throws子句
编辑:根据评论中的讨论,一个基于列表的示例揭示了根本原因:擦除
public class Main {
public static void main(String[] args) {
List<Exception> myCheckedExceptions = new ArrayList<Exception>();
myCheckedExceptions.add(new IOException());
// here we're tricking the compiler
@SuppressWarnings("unchecked")
List<RuntimeException> myUncheckedExceptions = (List<RuntimeException>) (Object) myCheckedExceptions;
// here we aren't any more, at least not beyond the fact that type arguments are erased
throw throwAny(myUncheckedExceptions);
}
public static <T extends Throwable> T throwAny(Collection<T> throwables) throws T {
// here the compiler will insert an implicit cast to T (just like your explicit one)
// however, since T is a type variable, it gets erased to the erasure of its bound
// and that happens to be Throwable, so a useless cast...
throw throwables.iterator().next();
}
}
公共类主{
公共静态void main(字符串[]args){
List myCheckedExceptions=new ArrayList();
添加(新IOException());
//我们在欺骗编译器
@抑制警告(“未选中”)
List myUncheckedExceptions=(List)(Object)myCheckedExceptions;
//在这里,我们再也没有了,至少没有超出类型参数被删除的事实
投掷throwAny(我未选中的感觉);
}
公共静态T throwAny(集合throwables)抛出T{
//在这里,编译器将向T插入一个隐式转换(就像显式转换一样)
//然而,由于T是一个类型变量,它会被擦除,直到其边界被擦除
//而这恰好是可以扔掉的,所以一个无用的演员。。。
抛出throwables.iterator().next();
}
}
所有这些都等于利用了一个漏洞,即未经检查的对泛型类型的强制转换不是编译器错误。如果您的代码包含这样的表达式,则会显式地使其类型不安全。而且,由于检查已检查的异常严格来说是一个编译时过程,因此在运行时不会有任何中断
泛型作者的答案很可能是“如果您使用的是未经检查的类型转换,那就是您的问题”
我从你的发现中看到了一些非常积极的东西——打破了被检查的异常的堡垒。不幸的是,这无法将现有的检查异常中毒API转变为更易于使用的API
这有什么帮助
在矿山的一个典型分层应用项目中,会有很多这样的样板文件:
try {
... business logic stuff ...
}
catch (RuntimeException e) { throw e; }
catch (Exception e) { throw new RuntimeException(e); }
我为什么要这样做?简单:没有需要捕获的业务价值异常;任何异常都是运行时错误的症状。异常必须沿着调用堆栈向上传播到全局异常屏障。有了卢卡斯的杰出贡献,我现在可以写作了
try {
... business logic stuff ...
} catch (Exception e) { throwUnchecked(e); }
这看起来可能不多,但在整个项目中重复100次之后,收益就会累积
免责声明
在我的项目中,对于异常有很高的要求,因此这非常适合它们这种技巧不能作为一般的编码原则。在许多情况下,包装异常仍然是唯一安全的选项。JLS 11.2:
对于每个检查的异常(可能的结果),抛出
方法条款(§8.4.6)或建造商条款(§8.8.5)必须提及
该异常的类或
该例外情况(§11.2.3)
这清楚地表明doThrow在其throws子句中必须有异常。或者,由于涉及向下转换(Exception to RuntimeException),必须检查该异常是否为RuntimeException,在示例中该异常应该失败,因为正在转换的异常是SQLException。因此,ClassCastException应该在运行时抛出
从实用的角度来看,这个java bug允许创建一个对任何标准异常处理代码都不安全的hack,如next:
try {
doCall();
} catch (RuntimeException e) {
handle();
}
异常将在未处理的情况下上升 本示例记录了几个不符合规范的代码示例 不合规代码示例(一般例外)
interface Thr<EXC extends Exception> {
void fn() throws EXC;
}
public class UndeclaredGen {
static void undeclaredThrow() throws RuntimeException {
@SuppressWarnings("unchecked") // Suppresses warnings
Thr<RuntimeException> thr = (Thr<RuntimeException>)(Thr)
new Thr<IOException>() {
public void fn() throws IOException {
throw new IOException();
}
};
thr.fn();
}
public static void main(String[] args) {
undeclaredThrow();
}
}
Exception e = new IOException();
throw (RuntimeException) (e);
static <E extends Exception> void doThrow0(E e) throws E {