为什么泛型参数“扩展”派生函数中不允许的内容,但泛型返回类型是?

时间:2011-06-07 01:09:55

标签: java generics

我想覆盖子类中的泛型函数,如下所示:

超类:

public abstract class Metric {

    public abstract <T extends Foo> T precompute(); // valid syntax
    public abstract <T extends Foo> void distance(T arg); // valid syntax
    public static class Foo {}

}

子类:

public class MetricDefault extends Metric {

    @Override
    public Bar precompute() { return new Bar(); } // Valid - return type extends Foo

    @Override
    public void distance(Bar arg) {} // Invalid ??? - argument type extends foo

    public static class Bar extends Metric.Foo {}

}

泛型函数,当泛型类型是函数的返回值时,是有效的Java代码并且构建成功。

仅更改泛型类型的位置 - 使其成为函数的参数,而不是返回类型 - 并且它变为无效的Java代码。

为什么后一种情况无效?我该怎么做才能实现这个功能?

5 个答案:

答案 0 :(得分:4)

方法声明

public abstract <T extends Foo> void distance(T arg);

表示该方法采用T参数,调用者可以决定将Foo的哪个子类型用作T

方法声明

public abstract <T extends Foo> T precompute();

表示该方法返回T类型的某个对象,调用方可以决定将Foo的哪个子类型用作T

最后一个方法无法真正实现(除了返回null),因为你的方法并不真正知道这里要生成什么对象。

在你的具体类中,你试图覆盖这些方法 - 根据我的理解,你不应该允许它们,但是编译器可能比我更轻松。

我认为您真正想要的是像 Overbose 所发布的那样。

答案 1 :(得分:3)

public abstract class Metric<T extends Foo> {

    public abstract  T precompute(); // valid syntax
    public abstract void distance(T arg); // valid syntax
    public class Foo {}

}

然后,如果我理解Bar延伸Foo:

public class MetricDefault extends Metric<Bar> {

    @Override
    public Bar precompute() { return new Bar(); } 

    @Override
    public void distance(Bar arg){} 

    public class Bar extends Foo {}

}

答案 2 :(得分:2)

因为超类中distance(T)的签名指定允许具有对Metric的引用的人传递任何扩展Foo的类型作为参数。

第一种方法可以正常工作,因为任何引用Metric的人都已准备好处理Bar的返回类型,因为签名是指父类型Bar

答案 3 :(得分:1)

为此,您需要使T成为该类型的参数,而不是方法。

您尝试这样做的方式,您的新方法不会覆盖抽象基本方法。执行类型擦除后,它等于以下代码:

public abstract class Metric {
    public abstract Foo precompute(); // valid syntax
    public abstract void distance(Foo arg); // valid syntax
    public class Foo {}
}

public class MetricDefault extends Metric {
    public Bar precompute() { return new Bar(); } // Valid - return type extends Foo
    public void distance(Bar arg) {} // Signature not compatible - does not override distance(Foo)
    public class Bar extends Foo {}
}

答案 4 :(得分:1)

正如@PaŭloEbermann所提到的,以下重写不应该是合法的

    <T extends Foo>
    T   precompute();

    Bar precompute()

JLSv3#8.4.8.3和#8.4.5中的规则禁止此覆盖。 Javac应该报告错误。

有两种可能的解释

  1. 规范省略了直观且明显的规则:“返回类型 - 可替代”关系应该是可传递的。 T->Foo->BarBar覆盖T是合法的。

  2. javac是越野车。在验证覆盖时,它以某种方式错误Foo作为方法#1的返回类型。

  3. 第一种解释非常难以置信。如果(经过定义的)关系是可传递的,那么检查它是否适用于2种任意类型就太难了。所以第二种解释可能更真实。