为什么 javac 在 return 类型的方法上使用有界类型泛型编译此代码?它是无效的类型推断吗?

Why is javac compiling this code with bounded type generics on the return type of a method? Is it an invalid type inference?

我们有类似下面的代码:

import java.io.Closeable;

public class Test {
    @FunctionalInterface
    public interface Action1<E extends Exception> {
        void run() throws E;
    }

    public interface Action2 {
        void run();
    }

    static class MyClass implements Action1<Exception>, Action2 {
        @Override
        public void run() {
        }
    }

    public static <C extends Action1<Exception> & Action2> C emptyAction() {
        return (C) new MyClass();
    }

    public static void main(String[] args) throws Exception {
// Compiler says: "error: incompatible types: MyClass cannot be converted to Closeable"
//        Closeable onClose1 = new MyClass();
//        onClose1.close();

// Compiles but ClassCastException at runtime:
// Exception in thread "main" java.lang.ClassCastException: class Test$MyClass cannot be cast to class java.io.Closeable (Test$MyClass is in unnamed module of loader // com.sun.tools.javac.launcher.Main$MemoryClassLoader @62fdb4a6; java.io.Closeable is in module java.base of loader 'bootstrap')
//  at Test.main(Test.java:27)
        Closeable onClose2 = emptyAction();
        onClose2.close();
    }
}

注意直接调用构造函数是如何被编译器正确拒绝的(onClose1),而方法 return 类型没有被拒绝,但随后继续抛出一个 ClassCastException运行时(onClose2)。我用 Java 11 和 Java 14 进行了测试,两次都得到了相同的结果。

我不明白为什么编译器接受编译onClose2?通用类型签名并不表示 returned 类型确实是 Closeable,它也没有创建充当桥梁的 lambda。我什至想知道擦除,但我看不出它在这里可以起到什么作用...

有人有什么解释吗?

首先:你在用界面做奇怪的事情。我强烈建议不要让接口具有相同的方法名称和 return 类型仅在 throws-statement 上不同。默认方法和 implements-statements 的顺序创造了一定的自由度。你用这两个接口表达的合约已经不可靠了

MyClass 也没有必要实现 @FunctionInterface。这就是 lambda 的用途:自动检查。

您应该尝试理解您的代码的两个问题:

  1. 删除硬转换为 C 会导致编译器错误,即 MyClass 无法转换为 C。
  2. 运行 具有所有显式类型暴力的代码仍然会产生 class 转换异常。

我认为您的代码根本就不是 运行 ——您已经指出缺少 implements Closeable。您想了解 lambda 和泛型的含义。这绝对超出了简短回答的可能性,但我可以给你两个重要提示:

  1. 泛型不是继承,因此 List<Number> 永远不会接受 LongList<? extends Number> 根本不接受任何东西。
  2. 任何类型,无论是接口还是 class,具有与 java.util.Function 具有完全相同的 signature 方法的方法都可以用作 java.util.Function 在 Lambda 表达式中。 (LangSpec §15.27.3)

所以你的 C 和 MyClass 除了类型参数中的相同字符外没有任何共享。现在该方法建议,任何赋值都将向后传播到 MyClass,就像您对 Closeable 的尝试一样。你的意思是:

public interface SuperInterface extends Action1, Action2 {
}

public static SuperInterface emptyAction() {
   return new MyClass();
}

泛型不能超越 classic 接口。他们只提供更详细的表达,例如 List 真正包含的内容。永远不要尝试用泛型来表达继承。这就是为什么你不能在 Map<String,Factory<T>> 中收集工厂 classes 并成为一个灵活的朋友,具体取决于任务。

Angelika Langer 有大量关于泛型的文章和资源,以及它们为何无法如您所愿地工作。我没有 link 任何特定页面,这与其说是阅读,不如说是一段旅程。

并且对于 lambda 尝试摄取 Java Language Specification。我在#2 中写了相关段落。根本没有 Lambda 表达式。它也不会修复您的代码。 Lambda 和异常不能一起工作。除了要抛出异常的本地/最小上下文之外,Lambda 表达式中没有异常路径。