JLS的哪些部分证明能够抛出已检查的异常,就好像它们未经检查一样?

Luk*_*der 29 java generics exception jls checked-exceptions

最近发现并写了一篇关于这样一个事实的博客:有可能通过javac编译器偷看一个已检查的异常,并将它扔到不能抛出它的地方.这在Java 6和7中编译并运行,抛出一个SQLExceptionwithout throwscatch子句:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

生成的字节码表示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
Run Code Online (Sandbox Code Playgroud)

接受这个的JVM是一回事.但我对Java-the-language是否应该存在疑问.JLS的哪些部分证明了这种行为?这是一个错误吗?还是Java语言隐藏的"功能"?

我的感受是:

  • doThrow0()<E>被绑定到RuntimeExceptiondoThrow().因此,不需要throws沿着线子句JLS§11.2是必要的doThrow().
  • RuntimeException与赋值兼容Exception,因此ClassCastException编译器不会生成强制转换(这将导致a ).

Mar*_*nik 9

所有这些都相当于利用了未经检查的强制转换为泛型类型的漏洞,而不是编译器错误.如果代码包含这样的表达式,那么它的代码是明确的类型不安全的.并且由于检查已检查的异常严格来说是编译时过程,因此运行时不会中断.

泛型作者的答案很可能就是"如果你使用未经检验的演员表,那就是你的问题".

我在你的发现中看到了一些非常积极的东西 - 突破了已检查异常的大本营.不幸的是,这不能将现有的检查异常中毒的API变成更令人愉快的使用方式.

这有什么用呢?

在我的一个典型的分层应用项目中,会有很多这样的样板:

try {
  ... business logic stuff ...
} 
catch (RuntimeException e) { throw e; } 
catch (Exception e) { throw new RuntimeException(e); }
Run Code Online (Sandbox Code Playgroud)

我为什么这样做?简单:没有业务价值例外可以捕获; 任何异常都是运行时错误的症状.该异常必须沿调用堆栈向上传播到全局异常障碍.有了Lukas的精彩贡献,我现在可以写了

try {
  ... business logic stuff ... 
} catch (Exception e) { throwUnchecked(e); }
Run Code Online (Sandbox Code Playgroud)

这可能看起来不多,但在整个项目中重复100次之后,收益会累积.

放弃

在我的项目中,有关于异常的高度纪律,所以这非常适合他们.这种技巧不能作为一般编码原则采用.在许多情况下,包装异常仍然是唯一安全的选择.

  • 这可以彻底打破那些希望声明检查异常的调用者.想象一下包含多个调用的try块.其中一个方法叫做`throws FooException`,另一个不会反正抛出它.处理`FooException`的catch块可能不会指望程序通过时所处的状态. (3认同)
  • *"已检查异常中毒的API"*;-) (2认同)

Ben*_*ulz 2

嗯,这是引发检查异常的多种方法之一,就像未检查一样。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();
    }
}
Run Code Online (Sandbox Code Playgroud)