【问题标题】:A peculiar feature of exception type inference in Java 8Java 8 中异常类型推断的一个特殊特性
【发布时间】:2016-10-30 08:12:20
【问题描述】:

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

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 调用?它们看起来非常相似。

【问题讨论】:

    标签: java generics java-8 type-inference


    【解决方案1】:

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

    首先,第 18.1.3 节有一个注释:

    throws α 形式的边界纯粹是信息性的:它指示解析以优化 α 的实例化,以便在可能的情况下,它不是检查异常类型。

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

    ...否则,如果绑定集包含throws αi,并且αi的正确上界最多为ExceptionThrowableObject,则Ti = RuntimeException

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

    nonSneakyThrow 无法编译,因为该方法的T 的下限为Exception(即T 必须是Exception 的超类型,或者Exception 本身),这是一个检查异常,由于它被调用的类型,所以T 被推断为Exception

    【讨论】:

    • @Maksym 您一定是指sneakyThrow 电话。 Java 7的规范中没有关于throws T形式推断的特殊规定。
    • 小问题:在nonSneakyThrow 中,T 必须是Exception,而不是Exception 的“超类型”,因为它正是在编译时声明的参数的类型呼叫站点。
    • @lllogiq 如果我正确阅读了规范,它的下限为Exception 和上限为Throwable,所以最小上限,即推断的类型,是Exception
    • @lllogiq 请注意,参数的类型只设置了一个下限类型,因为参数的任何超类型也是可以接受的。
    • “或Exception 本身”短语可能对读者有所帮助,但通常应该注意,规范总是在“包括”的意义上使用术语“子类型”和“超类型”本身”……
    【解决方案2】:

    如果类型推断为类型变量生成单个上限,则通常选择上限作为解决方案。例如,如果T&lt;&lt;Number,则解决方案为T=Number。尽管IntegerFloat 等也可以满足约束条件,但没有充分的理由选择它们而不是Number

    Java 5-7 中的throws T 也是如此:T&lt;&lt;Throwable =&gt; T=Throwable。 (偷偷摸摸的抛出解决方案都有明确的&lt;RuntimeException&gt; 类型参数,否则推断&lt;Throwable&gt;。)

    在 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 会被推断出来。请参阅我提交的report

    但是,从一个空块中推断出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 作为解。在这种情况下, 有充分的理由选择上限的特定子类型。

    【讨论】:

    • 你上一个例子 sn-p 中X:&gt;RuntimeException 的含义是什么?
    • @marsouf - X 具有 RuntimeException 的下限。
    【解决方案3】:

    使用sneakyThrow,类型T 是有界泛型类型变量没有特定类型(因为没有类型可以来自哪里)。

    对于nonSneakyThrowT 的类型与参数的类型相同,因此在您的示例中,nonSneakyThrow(e);TException。由于testSneaky() 没有声明抛出的Exception,因此会显示错误。

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

    【讨论】:

    • 所以对于sneakyThrow,它实际上并没有被推断为任何特定类型,而“演员”是这样一个未定义的类型?我想知道这到底会发生什么。
    猜你喜欢
    • 2019-07-18
    • 2014-08-17
    • 1970-01-01
    • 1970-01-01
    • 2017-08-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多