8

为什么这段代码无法编译?

public class x
{
    private void test()
    {
        handle(new ThingA());
        handle(new ModifiedThingA());
    }

    private <T extends BaseThing<T>, X extends T> java.util.List<T> handle(X object)
    {
        return object.getList();
    }

    private static class BaseThing<T extends BaseThing<T>>
    {
        public java.util.List<T> getList()
        {
            return null;
        }
    }

    private static class ThingA
        extends BaseThing<ThingA>
    {
    }

    private static class ModifiedThingA
        extends ThingA
    {
    }
}

Java 6 给出了这个错误handle(new ModifiedThingA());

x.java:6: <T,X>handle(X) in x cannot be applied to (x.ModifiedThingA)
            handle(new ModifiedThingA());
            ^

Java 7 甚至不喜欢handle(new ThingA());,这是 Java 7 的输出:

x.java:5: error: invalid inferred types for T; inferred type does not conform to declared bound(s)
            handle(new ThingA());
                  ^
    inferred: ThingA
    bound(s): CAP#1
  where T,X are type-variables:
    T extends BaseThing<T> declared in method <T,X>handle(X)
    X extends T declared in method <T,X>handle(X)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends BaseThing<CAP#1> from capture of ?
x.java:6: error: invalid inferred types for T; inferred type does not conform to declared bound(s)
            handle(new ModifiedThingA());
                  ^
    inferred: ModifiedThingA
    bound(s): CAP#1
  where T,X are type-variables:
    T extends BaseThing<T> declared in method <T,X>handle(X)
    X extends T declared in method <T,X>handle(X)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends BaseThing<CAP#1> from capture of ?
2 errors

在我看来,当它实际上是a时,它javac被误认为是 a 。这是我的错误还是我的?ModifiedThingABaseThing<ModifiedThingA>BaseThing<ThingA>javac

4

3 回答 3

1

javac的行为似乎是正确的。理论上,一个类型变量 ,T就足够了。但是,您引入了第二个类型变量X,以帮助进行类型推断。首先推断出for 的参数X,然后根据调用上下文T推断出 for 的参数:

List<ThingA> a = handle(new ThingA());
List<ThingA> b = handle(new ModifiedThingA());

但是,您的调用上下文不会对返回类型设置任何限制。因此,编译器被迫引入一个类型变量 ( CAP#1),其下限为 null。T的论点将被推断为glb(BaseThing<CAP#1>) = BaseThing<CAP#1>。鉴于其下限,X不能证明是 的子类型T

有两种或三种方法可以解决这个问题。

  1. 手动推断(如果很少需要也可以)
  2. 提供返回类型为 void 的“重载”(需要另一个名称,呃)
  3. 如果返回的列表是不可变的或防御性副本,您可以断开 type 参数T与其 bounds 参数的连接

我更喜欢选项3:

private <T extends BaseThing<T>> List<T> handle(BaseThing<? extends T> object) {
    return new ArrayList<T>(object.getList());
    // or (using guava's ImmutableList)
    return ImmutableList.copyOf(object.getList());
}

快乐的泛型。

于 2012-08-21T18:48:28.323 回答
1

您的代码在 javac 1.8.0_45 和 Eclipse 4.1.1 中编译得很好。

可能为了以一种很好的方式编译 lambda 表达式而对 Java 8 中的类型推断算法所做的更改也解决了您的问题。

于 2015-11-04T12:44:02.767 回答
0

javac 1.7.0_09 和 1.7.0_11 似乎有一个bug,导致了这个问题。

于 2013-01-21T16:48:39.820 回答