英文:
Why does the compiler not enforce the return type value for a generic that extends an interface?
问题
public class Test {
public static void main(String... args) {
ClassB clazzB1 = getClassA(); // 编译错误
ClassB clazzB2 = getInterfaceC1(); // 编译错误
ClassB clazzB3 = getInterfaceC2(); // 无编译错误
}
public static <T extends ClassA> T getClassA() {
return (T) new ClassA() {};
}
public static InterfaceC getInterfaceC1() {
return new InterfaceC() {};
}
public static <T extends InterfaceC> T getInterfaceC2() {
return (T) new InterfaceC() {};
}
private static abstract class ClassA {
}
private static class ClassB {
}
}
public interface InterfaceC {
}
为什么将 getInterfaceC2() 赋值给 clazzB3 时没有编译错误?它似乎应该出现编译错误,而我希望出现这种情况。
<details>
<summary>英文:</summary>
public class Test {
public static void main(String... args) {
ClassB clazzB1 = getClassA(); // compile error
ClassB clazzB2 = getInterfaceC1(); // compile error
ClassB clazzB3 = getInterfaceC2(); // no compile error
}
public static <T extends ClassA> T getClassA() {
return (T) new ClassA() {};
}
public static InterfaceC getInterfaceC1() {
return new InterfaceC() {};
}
public static <T extends InterfaceC> T getInterfaceC2() {
return (T) new InterfaceC() {};
}
private static abstract class ClassA {
}
private static class ClassB {
}
}
public interface InterfaceC {
}
Why does getInterfaceC2() not give a compile error when assigned to clazzB3? It seems like it should and is something that I would like to have happen.
</details>
# 答案1
**得分**: 1
你总是可以这样做:
```java
static class InterestingType extends ClassB implements InterfaceC {
}
然后有:
public static <T extends InterfaceC> T getInterfaceC2() {
return (T)new InterestingType();
}
因此调用:
ClassB clazzB3 = getInterfaceC2();
会起作用。在你的情况下,你明白它会出问题,但编译器无法轻易证明这一点。因此,你的示例是允许的,但会在运行时失败。
编辑
编译器看到这个:
ClassB clazzB1 = getClassA();
并且看到getClassA()
返回一个被定义为T extends ClassA
的T
,其中ClassA
是一个类。它还看到这个T
被分配给ClassB
,其中ClassB
也是一个类。所以它必须创建一个符合ClassA
和ClassB
的特定类型,类似于一个理论上的:
T extends ClassA & ClassB
但这是不可能的类型,因为没有人可以做... extends ClassA, ClassB
(在Java中根本不允许这样)。因此编译器失败了。实际上,如果你执行:
javac --debug=verboseResolution=all Test
你会在输出中看到类似以下内容:
error: incompatible types: inference variable T has incompatible upper bounds ClassB, ClassA
另一方面,当编译器看到这个:ClassB clazzB3 = getInterfaceC2();
,它看到getInterfaceC2()
返回:
T extends InterfaceC
并且结果被分配给ClassB
,所以最终可能得到这种类型:
T extends ClassB & InterfaceC
这是完全有效的。确实可以有这样的类型。我已经向你展示了你可以通过InterestingType
创建这样一个类型,但正如Holger所指出的,你可以通过以下方式自己传递这样一个理论类型:
ClassB clazzB3 = Test.<InterestingType>getInterfaceC2();
这被称为指定一个“类型证明”。
英文:
You can always do:
static class InterestingType extends ClassB implements InterfaceC {
}
And then have :
public static <T extends InterfaceC> T getInterfaceC2() {
return (T)new InterestingType();
}
As such calling:
ClassB clazzB3 = getInterfaceC2();
would work. In your case it is obvious to you that it will break, but the compiler can't prove that (easily). As such, your example is allowed, but will fail at runtime.
EDIT
The compiler looks at this:
ClassB clazzB1 = getClassA();
and sees that getClassA()
returns a T
that is defined as T extends ClassA
, where ClassA
is a class. It also sees that this T
is assigned to ClassB
, where ClassB
is a class also. So it has to create a certain type that will conform to both ClassA
and ClassB
, something like a theoretical :
T extends ClassA & ClassB
but that is an impossible type, since no one can do ... extends ClassA, ClassB
(this is simply not allowed in java). So the compiler fails. As a matter of fact if you do :
javac --debug=verboseResolution=all Test
you will see in the output, something like:
> error: incompatible types: inference variable T has incompatible upper bounds ClassB,ClassA
On the other hand, when the compiler looks at this : ClassB clazzB3 = getInterfaceC2();
, it sees that getInterfaceC2()
returns:
T extends InterfaceC
and the result is assigned to ClassB
, so it ends up with may be this type:
T extends ClassB & InterfaceC
which is perfectly valid. There can be such a type. I've shown you that you can create one as InterestingType
, but as Holger pointed out, you could pass such a theoretical type yourself, via:
ClassB clazzB3 = Test.<InterestingType>getInterfaceC2();
this is called specifying a "type witness".
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论