Java泛型类型推断混淆

时间:2017-02-22 09:18:43

标签: java type-inference generic-programming

全部, 为了测试我在下面创建的Java Generics Type Inference示例代码。

public static <U> U addBox2(U u1, U u2) {
    return u2;
}

static interface A {}
static interface B {}
static class S {}
static class C extends S implements A,B {}
static class D extends S implements B,A {}

如果你看到上面的A和S没有任何关系。

在下面的代码中 -

A a = addBox2(new C(),new D());

我希望收到编译错误,因为推断的类型是S并且我将它分配给A和A并且S没有任何关系,这仍然可以正常工作。

有人可以帮我解释一下这种行为的原因吗?

4 个答案:

答案 0 :(得分:2)

根据要求:

AS确实没有关系,但是因为您将CD传递给该方法,并且都实现了A(和B)他们有一些共同点,即它们都是A

这意味着以下内容可行:

A a = addBox2(new C(),new D()); //common type is A
B b = addBox2(new C(),new D()); //common type is B
S s = addBox2(new C(),new D()); //common type is S
Object o = addBox2(new C(),new D()); //common type is Object

只要类型推断可以解决赋值中的泛型类型以及它应该工作的参数(请注意,在8之前的Java版本中,类型推断不是很好,特别是在5和6中)。

但是,您可以通过将类型传递给方法调用来自定义类型:

A a = EnclosingClass.<S>addBox2(new C(),new D()); //static method within EnclosingClass
A a = this.<S>addBox2(new C(),new D()); //instance method

在这两种情况下,您都将通用类型定义为S,因此分配将无效。

答案 1 :(得分:0)

类型参数U是根据您将a的结果分配到的addBox2(...)变量的类型推断出来的,在您的情况下为A。由于CD都实现了A,因此它可以完美运行。

以下当然不起作用:

S s = addBox2(new C(),new D());
A a = s; // compile error, type mismatch

答案 2 :(得分:0)

我怀疑Eclipse和IntelliJ没有正确地注释这些。有了这个(稍微调整过)的例子:

public static <U> U getU(U u1, U u2) {
  return u2;
}

static interface A {}
static class S {}
static class C extends S implements A {}
static class D extends S implements A {}
static class T implements A {}

public static void main(String[] args) {
  A a = getU(new C(), new D());
  A b = getU(new C(), new T());
}

当我在getU()中第一次拨打main()时,我看到以下内容:

  

<? extends S> ? extends S analysis.Delme.getU(? extends S u1, ? extends S u2)

这显然不正确 - A不会延伸S。我怀疑Eclipse错误地过分简化了这里的泛型,并且更愿意报告CD都来自S,而不是同时实现A

如果我鼠标悬停在第二个电话上,我会更加合理:

  

<A> A analysis.Delme.getU(A u1, A u2)

这可能是编译器实际在这里为两个调用做的事情。

注意Thomas指出,即使两个类没有任何共同点(例如ST),它们仍然会从Object延伸,所以{{1是有效的。

答案 3 :(得分:0)

不要相信编辑器的工具提示可能无法处理复杂的通用结构。推断类型为S & A & B(在Java 8之前),可以将其分配给A。在Java 8中,推断类型只是A,因为泛型方法调用是使用目标类型的所谓多重表达式。对于addBox调用,它没有任何区别。

为了说明问题,您可以写

Object o = Arrays.asList(new C(), new D());

在Eclipse中使用Java 7合规性模式,并将鼠标悬停在asList上。它将打印
<? extends S> List<? extends S> java.util.Arrays.asList(? extends S... a),类似于问题的addBox调用,但是当您将代码更改为

List<A> list = Arrays.asList(new C(), new D());

您收到编译器错误“类型不匹配:无法从List<S&A&B>转换为List<A>”(仍处于Java 7模式),表明实际推断类型为S & A & B,而不是{ {1}}。

当您将语言合规性级别转换为Java 8时,编译器错误将消失,就像在Java 8中一样,目标类型将用于推断类型,它将是? extends S而不是{{1 }}

如前所述,对于A调用,无论是S & A & B还是addBox都是无误的,因为两者都可以分配到A,因此可以在Java 7和Java 8合规级别。但是当你使用调用时,它会产生差异,就像使用S & A & B一样,你可以看到实际推断的类型,它永远不会像编辑器的工具提示所声称的那样A ......