[英]Why does Java allow this code with generics to compile?
我最近被以下 Java 代码弄得措手不及:
interface Common {}
interface A extends Common {}
static class B implements Common {}
static class Impl {
private A a;
public <T extends A> T translate() {
return (T) a;
}
}
static class Usage {
public void use() {
Impl impl = new Impl();
B b = impl.translate(); // Why does this compile?
}
}
考虑到B
不扩展A
,我本来希望Impl.translate
上的类型约束不允许将结果存储在类型B
中以被编译器接受。 代码在运行时抛出UncheckedCastException
而不是编译器错误。
这只发生在方法返回类型T
时; 如果它是方法参数,而不是:
public <T extends A> void translate(T t) {}
然后,正如预期的那样,不允许B
的实例作为 t translate
的参数。
这里发生了什么? 为什么 Java 的类型系统允许这样做?
这是可以编译的,因为原则上 object 可能既是B
又是A
。 例如,此 class 的一个实例:
static class C extends B implements A {}
对于编译器来说,不存在这样的 class 并不重要。 其他人可能会从依赖项导入此代码并自己定义C
,这是有效的,必须允许工作。 object 的实际 class 在编译时很容易找到并不重要,因为编译器不会进行那种分析。 禁止存在此类 class 的修饰符(例如将final
添加到B
)也未考虑在内,尽管我不确定为什么。 这可能只是为了降低编译器逻辑的复杂性。
将A
更改为 class 会导致编译错误,因为 Java 不允许 class 扩展多个类。
您的通用T
没有被分配。 由于可能有一个B
和A
的值,编译器假设你没问题。
如果将类型分配给B
它,则会出现错误。
B b = impl.<B>translate();
Testy.java:16:错误:class Impl 中的方法转换不能应用于给定类型; B b = impl.translate(); ^
必需:无 arguments
发现:没有 arguments
原因:显式类型参数 B 不符合声明的边界 A
当类型由参数确定时,您会遇到类似的错误。
public <T extends A> T translate(T t) {
return (T) a;
}
B b = impl.translate(new B());
类型是由参数分配的,参数是B
这不会编译。
如另一个答案中所述,如果A
和B
都是类,则不会有T
可以同时满足两者。
Testy.java:15:错误:不兼容的类型:推理变量 T 具有不兼容的上限 B,A
B b = impl.translate(); // 为什么会编译?
^
其中 T 是一个类型变量:
T 扩展了方法 translate() 中声明的 A
TL;DR 答案
“关键是可能存在一个扩展 [
B
] 并实现 [A
] 的 class,而理论类型就是T
的推断。” — @JornVernee 2017 年 7 月 3 日 14:43
长答案
Jorn Vernee 实际上在 Java 团队的 Oracle 工作。 他在TL;DR中引用的评论是他雇主第 18 章中最简洁、最容易理解的提炼。你会发现类型推断。
如果你对类型系统和 lambda 微积分了如指掌,并且想要完整的细节,那么第 18 章就在你身边。
在 Vernee 的TL;DR的专家简洁和 JLS 令人头痛的严谨性之间,是用户 @Radiodef 对 JLS 的缩减、合并和解决过程的逐点摘要。
@JornVernee 和 @Radiodef 对 JLS 在这个谜题背后的推理的解释,是(对我而言)这个问题的七个或八个(至少)重复的几十个其他答案中最清晰、最容易理解的:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.