Java 8中异常类型推断的独特功能


85

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

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;
}

首先,我很困惑为什么sneakyThrow对编译器的调用正常。T当未提及任何未经检查的异常类型时,它推断出什么可能的类型?

其次,接受这一工作原理后,为什么编译器会在nonSneakyThrow调用中抱怨?他们看起来非常相似。

Answers:


66

的TsneakyThrow推断为RuntimeException。可以从有关类型推断的语言规范(http://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html)中遵循此规则。

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

形式的边界throws α纯粹是信息性的:它指导分辨率优化α的实例化,因此,如果可能的话,它不是经过检查的异常类型。

这没有任何影响,但它使我们指向了“解析”部分(18.4),该部分获得了有关特殊情况下的推断异常类型的更多信息:

......否则,如果绑定集包含throws αi,和α我的正常上限的,顶多ExceptionThrowableObject,然后TI = RuntimeException

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

nonSneakyThrow不会编译,因为该方法的T下限为Exception(即T必须是ExceptionException自身的超类型),这是一个已检查的异常,这是由于调用该方法的类型而导致的,因此T推断为Exception


1
@Maksym你一定要打个sneakyThrow电话。关于推理的特殊法规throws T的形式并没有在Java 7中的规范存在
马尔科Topolnik

1
Small nitpick:在中nonSneakyThrowT必须为Exception,而不是“的超类” Exception,因为它恰好是在调用时在编译时声明的参数的类型。
llogiq

1
@llogiq如果我正确阅读了规范,则它的下限为Exception,上限为Throwable,因此最小的上限(即推断出的类型)为Exception
thecoop

1
@llogiq注意,参数的类型仅设置一个下限类型边界,因为参数的任何超类型也是可以接受的。
Marko Topolnik

2
“或Exception自身”一词可能对读者有帮助,但通常应注意的是,说明书中始终以“包括自身”的含义使用术语“亚型”和“超型”……
Holger

17

如果类型推断为类型变量产生单个上限,则通常选择上限作为解决方案。例如,如果T<<Number,则解决方案是T=Number。虽然IntegerFloat等,也能满足约束条件,没有充分的理由选择了他们Number

也是在那情况下throws T在java中5-7: T<<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( ()->{} ); 

唯一的限制T是上限Throwable。在java8的早期阶段,T=Throwable将进行推断。请参阅我提交的这份报告

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

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)

即,如果上限是ExceptionThrowable,则选择RuntimeException作为解决方案。在这种情况下,一个很好的理由来选择上限的特定亚型。


X:>RuntimeException您上一个示例代码段的含义是什么?
marsouf

1

使用sneakyThrow,类型T没有特定类型的有界泛型类型变量(因为该类型可能来自何处)。

withnonSneakyThrow的类型T与参数相同,因此在您的示例中,TofnonSneakyThrow(e);Exception。由于testSneaky()未声明thrown Exception,因此显示错误。

请注意,这是带有检查异常的泛型的已知干扰。


因此,因为sneakyThrow它实际上没有推断为任何特定类型,并且“ cast”是否为此类未定义类型?我不知道这实际上会发生什么。
Marko Topolnik
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.