Java JLS的哪些部分可以证明能够像未检查异常一样抛出已检查异常?

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

我知道可以通过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 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 {