java - 为什么 javac 在方法的返回类型上使用有界类型泛型编译此代码?它是无效的类型推断吗?
问题描述
我们有类似于以下的代码:
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
请注意,编译器如何正确拒绝直接调用构造函数ClassCastException
(onClose2
我使用 Java 11 和 Java 14 进行了测试,两次都得到了相同的结果。
我不明白为什么编译器接受编译onClose2
?泛型类型签名并不表示返回的类型确实是Closeable
,它也没有创建充当桥梁的 lambda。我什至想知道擦除,但我不知道它可以在这里扮演什么角色......
有人有什么解释吗?
解决方案
首先:你正在用界面做奇怪的事情。我强烈建议不要使用具有相同方法名称和返回类型仅在 throws-statement 中不同的接口。默认方法和实现语句的顺序创建了一定程度的自由度。你通过这两个接口表达的合约不再可靠。
MyClass 也不需要实现@FunctionInterface
. 这就是 lambdas 的用途:自动检查。
您应该尝试了解代码的两个问题:
- 将硬转换为 C 会导致编译器错误,即 MyClass 无法转换为 C。
- 使用所有显式类型暴力运行代码仍然会产生类转换异常。
我认为您的代码根本不是用来运行的——您已经指出缺少implements Closeable
. 您想了解 lambda 和泛型的含义。这绝对超出了简短回答的可能性,但我可以给你两个重要提示:
- 泛型不是继承,因此 a
List<Number>
永远不会接受 aLong
。而 aList<? extends Number>
根本不接受任何东西。 - 任何具有与 java.util.Function 完全相同签名的方法的类型,无论是接口还是类,都可以用作 Lambda 表达式中的 java.util.Function。(语言规范§15.27.3)
因此,除了类型参数中的相同字符外,您的 C 和 MyClass 不共享任何内容。现在该方法表明,任何分配都将向后传播到 MyClass,就像您尝试使用 Closeable 一样。你的意思是:
public interface SuperInterface extends Action1, Action2 {
}
public static SuperInterface emptyAction() {
return new MyClass();
}
泛型不能超越经典接口。它们只提供更详细的表达式,例如 aList
真正包含的内容。永远不要试图用泛型来表达继承。这就是为什么你不能在Map<String,Factory<T>>
一个灵活的朋友中收集工厂课程,这取决于任务。
Angelika Langer有很多关于泛型的文章和资源,以及它们为什么不能按您期望的那样工作。我没有链接任何特定页面,这更像是一次旅程而不是阅读。
对于 lambdas 尝试摄取Java Language Specification。我在#2 中写了相关段落。根本没有 Lambda 表达式。它也不会修复您的代码。Lambda 和异常不能一起工作。除了要抛出异常的本地/最小上下文之外,Lambda 表达式中没有异常路径。
推荐阅读
- laravel - 如何在查询构建器的 get() 中解析数组
- ios - 在 ios 上编译 React 本机项目的问题
- android - 如何更改标题在工具栏中的位置?
- flutter - 如何创建深度大于 1 的可诊断树?
- javascript - 如何在javascript中更改列表标签的浮动样式?
- npm - 使 npm 库保持最新
- wordpress - 通过 POST 执行 SQL 查询
- docker - Docker push:使用多个标签推送一次图像
- node.js - 带有graphql和sqs的lambda在nodejs中向sqs发送2条消息?
- azure - 在 Azure 设备预配服务中为多个设备使用相同的 X509 证书