Java 8 中异常类型推理的一个特殊功能

2022-08-31 11:39:18

在这个网站上为另一个答案编写代码时,我遇到了这个特点:

static void testSneaky() {
  final Exception e = new Exception();
  sneakyThrow(e);    //no problems here
  nonSneakyThrow(e); //ERRROR: Unhandled exception: java.lang.Exception
}

@SuppressWarnings("unchecked")
static <T extends Throwable> void sneakyThrow(Throwable t) throws T {
  throw (T) t;
}

static <T extends Throwable> void nonSneakyThrow(T t) throws T {
  throw t;
}

首先,我很困惑为什么调用对编译器是可以的。当任何地方都没有提到未经检查的异常类型时,它推断出什么可能的类型?sneakyThrowT

其次,接受这有效,那么为什么编译器在调用时抱怨呢?它们看起来非常相似。nonSneakyThrow


答案 1

的 T 被推断为 。这可以从类型推断的语言规范中遵循(http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.htmlsneakyThrowRuntimeException)

首先,在18.1.3节中有一个注释:

窗体的边界纯粹是信息性的:它指示分辨率以优化α的实例化,以便如果可能,它不是已检查的异常类型。throws α

这不会影响任何内容,但它将我们指向“解决方案”部分(18.4),该部分提供了有关推断异常类型(具有特殊情况)的更多信息:

...否则,如果绑定集包含 ,并且 αi 的适当上限最多是 、 和 ,则 Ti = 。throws αiExceptionThrowableObjectRuntimeException

这种情况适用于 - 唯一的上限是 ,因此推断为符合规范,因此它进行编译。该方法的主体是无关紧要的 - 未经检查的强制转换在运行时成功,因为它实际上并没有发生,留下一个可以击败编译时检查异常系统的方法。sneakyThrowThrowableTRuntimeException

nonSneakyThrow不会编译,因为该方法的下限为(即必须是 的超类型或自身),这是一个经过检查的异常,因为它被调用的类型,因此被推断为 。TExceptionTExceptionExceptionTException


答案 2

如果类型推断为类型变量生成单个上限,则通常选择上限作为解决方案。例如,如果 ,则解为 。虽然 等 也可以 满足 约束, 但没有充分的理由选择它们。T<<NumberT=NumberIntegerFloatNumber

在java 5-7中也是如此:。(偷偷摸摸的解决方案都有明确的类型参数,否则是推断出来的。throws TT<<Throwable => T=Throwable<RuntimeException><Throwable>

在java8中,随着lambda的引入,这变得有问题。考虑这种情况

interface Action<T extends Throwable>
{
    void doIt() throws T;
}

<T extends Throwable> void invoke(Action<T> action) throws T
{
    action.doIt(); // throws T
}    

如果我们使用空的 lambda 调用,那么可以推断出什么?T

    invoke( ()->{} ); 

上的唯一约束是 上限 。在java8的早期阶段,可以推断出来。请参阅我提交的这份报告TThrowableT=Throwable

但是,从空块中推断出一个检查的异常是相当愚蠢的。报告中提出了一个解决方案(显然已被JLS采用) -Throwable

If E has not been inferred from previous steps, and E is in the throw clause, 
and E has an upper constraint E<<X,
    if X:>RuntimeException, infer E=RuntimeException
    otherwise, infer E=X. (X is an Error or a checked exception)

即,如果上限为 或 ,则选择作为解。在这种情况下,有充分的理由选择上限的特定子类型。ExceptionThrowableRuntimeException


推荐