为什么以下代码示例无法编译?
试样-1
public class Generic5<T extends Comparable, Runnable> {
T t;
Comparable comparable;
Generic5(T t) {
this.t = t;
}
Generic5(Comparable comparable) {
this.comparable = comparable;
}
}
样品-2
public class Generic4<T extends Runnable, Comparable> {
T t;
Runnable runnable;
Generic4(T t) {
this.t = t;
}
Generic4(Runnable runnable) {
this.runnable = runnable;
}
}
以下代码示例编译成功?
试样-4
public class Generic4<T extends Runnable, Comparable> {
T t;
Comparable comparable;
Generic4(T t) {
this.t = t;
}
Generic4(Comparable comparable) {
this.comparable = comparable;
}
}
试样-5
public class Generic5<T extends Comparable, Runnable> {
T t;
Runnable runnable;
Generic5(T t) {
this.t = t;
}
Generic5(Runnable runnable) {
this.runnable = runnable;
}
}
样品-6
public class Generic6<T extends Number, Comparable, Runnable> {
T t;
Comparable comparable;
Generic6(T t) {
this.t = t;
}
Generic6(Comparable comparable) {
this.comparable = comparable;
}
}
样品-7
public class Generic6<T extends Number, Comparable, Runnable> {
T t;
Runnable runnable;
Generic6(T t) {
this.t = t;
}
Generic6(Runnable runnable) {
this.runnable = runnable;
}
}
我无法理解为什么在样本4,5,6和7的情况下擦除没有问题,而在样本1和2的情况下则没有。
我正在使用Java 8.我希望对每个样本单独解释。
答案 0 :(得分:0)
阅读完评论后,这似乎只是对泛型的,
符号的误解。
考虑以下两个陈述:
class A <T extends B, C> { ... }
和
class X <U extends Y & Z> { ... }
在第一种情况下,将指定2个类型参数:
T
,其中包含B
C
,其中包含java.lang.Object
因此,要使用A
,必须指定类似的类型:
A<B, Object> myA = new A<>();
myA.myMethod();
如您所见,有两种通用类型需要。
对比是第二种情况,其中指定了1个类型参数:
U
,其中包含Y
和Z
要使用它,必须指定类似的类型:
class MyClass extends Y implements Z { ... }
// ...
X<MyClass> myX = new X<>();
myX.myMethod();
在这种情况下,只需要一种通用类型。该类型必须属于Y
和Z
类型,因此创建MyClass
以满足此要求的原因。
通过查看您的问题,我假设您打算指定第二种行为;但是,你不小心指定了第一个。
因此,如果我们考虑Sample-1,应该清楚它失败的原因。通过删除泛型类型(类型擦除,编译器必须做的事情),样本1将有效地变为:
public class Generic5 {
Comparable t;
Comparable comparable;
Generic5(Comparable t) {
this.t = t;
}
Generic5(Comparable comparable) {
this.comparable = comparable;
}
}
现在,如果我们调用一个类型为Comparable
的构造函数,JVM应该执行哪个? Java编译器无法分辨。这就是为什么不能编译这个样本的原因。 Java编译器有一个如何指定它的问题。
注意:在Sample-1中,Runnable
类型不是 java.lang.Runnable
。您不小心创建了具有相同名称的新泛型类型。这个Runnable
实际上会成为java.lang.Object
!
这实际上与指定类型的行为相同:
public class Generic5<T extends Comparable, U> { ... }
现在,如果我们考虑Sample-5并执行类型擦除,我们可以看到此示例的工作原理。 Sample-5将成为:
public class Generic5 {
Comparable t;
Object runnable;
Generic5(Comparable t) {
this.t = t;
}
Generic5(Object runnable) {
this.runnable = runnable;
}
}
现在,编译器很乐意接受这段代码!
与以前一样,Runnable
是您创建的新类型,将替换为java.lang.Object
,不是 java.lang.Runnable
。
因此,我们尝试修复Sample-1。立即解决方案是使用Multiple Type Parameter Bounds。这样就可以使类看起来像(注意第一行&
的用法):
public class Generic5<T extends Comparable & Runnable> {
T t;
Comparable comparable;
Generic5(T t) {
this.t = t;
}
Generic5(Comparable comparable) {
this.comparable = comparable;
}
}
现在,T
必须是java.lang.Comparable
和java.lang.Runnable
类型。我们设法使T
更加具体。我们还确保我们现在实际使用java.lang.Runnable
。
然而,这仍然没有成功!类型擦除仍然与以前相同,除了现在增加了java.lang.Runnable
的用法。
那为什么会失败呢?嗯,它与类型的顺序有关。由于java.lang.Comparable
首先出现,因此您通过说T
的主要特征具有可比性来指导Java编译器。执行类型擦除时,它会将T
替换为java.lang.Comparable
。在我们的第一个解决方案中,我们重新排列顺序并创建主要特征java.lang.Runnable
,编译器接受我们的帮助,一切都很好。在第二个中,我们用一个非常特定的新类型直接替换T
,Java编译器将比第一个更喜欢这种变化。
有两种方法可以解决这个问题:
public class Generic5<T extends Runnable & Comparable> {
T t;
Comparable comparable;
Generic5(T t) {
this.t = t;
}
Generic5(Comparable comparable) {
this.comparable = comparable;
}
}
这里,类型擦除将有效地变为:
public class Generic5 {
Runnable t;
Comparable comparable;
Generic5(Runnable t) {
this.t = t;
}
Generic5(Comparable comparable) {
this.comparable = comparable;
}
}
哪个会好的。
注意:t
仍然保证为java.lang.Comparable
,因此java.lang.Comparable
中的方法仍然可以在t
上调用。
可以放弃泛型以支持新类型:
public class Generic5 {
MyNewType t;
Comparable comparable;
Generic5(MyNewType t) {
this.t = t;
}
Generic5(Comparable comparable) {
this.comparable = comparable;
}
}
class MyNewType implements Comparable, Runnable { ... }
此处,泛型已完全删除。在我看来,这是更好的解决方案。它更接近于面向对象的编程,并消除了对类型的任何混淆。
如果要指定具有多个边界的泛型类型,则需要使用&
运算符,而不是,
运算符。
此外,当涉及类型擦除时,边界的顺序很重要。将使用第一个界限。如果仍然存在冲突,请重新排列顺序,或者创建一个扩展边界的新类,并使用该类代替泛型。