为什么编译器不对扩展接口的泛型强制执行返回类型值?

huangapple go评论111阅读模式
英文:

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 ClassAT,其中ClassA是一个类。它还看到这个T被分配给ClassB,其中ClassB也是一个类。所以它必须创建一个符合ClassAClassB的特定类型,类似于一个理论上的:

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 &lt;T extends InterfaceC&gt; 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 &amp; 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 &amp; 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.&lt;InterestingType&gt;getInterfaceC2();

this is called specifying a "type witness".

huangapple
  • 本文由 发表于 2020年10月16日 22:42:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/64391346.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定