2011-06-07 130 views
3

我想重写子类中的通用功能,如下所示:为什么泛型参数“扩展”派生函数中不允许的东西,但泛型返回类型是?

超类:

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代码。

为什么后者无效?我能做些什么来实现这个功能?

+2

下面有很好的解决方案,但我想补充一点,你的OP的问题是你通过指定一个子类型'Foo'来重载**方法'distance'。 NetBeans抱怨'@ Override'不应该在那里,因为'distance'不会覆盖超类中的任何方法。 – toto2 2011-06-07 01:58:28

回答

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 {} 

} 

然后如果我的理解酒吧延伸的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 {} 

} 
+0

就是这样,太棒了!一个小细节 - 您的答案中的代码应该读取为和Metric 。 – 2011-06-07 01:23:02

2

由于distance(T)在超类中的签名指定某人具有一个Metric参考应该被允许通过其延伸的任何类型的作为参数的Foo

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

+0

我打算在超类中签名'distance(T)'指定任何引用“Metric”的人都应该能够传递任何扩展'Foo'的类型。我不明白为什么这是一个问题。在我的子类中,函数事实上被定义为传递一个扩展'Foo'的类型。也许你(或某人)可以详细说明? – 2011-06-07 01:55:28

+0

如果你有'class Bizbat extends Foo',我可以把它传递给'MetricDefault.distance(Bar)'吗?该方法的实现无疑会导致ClassCastException。 “在我的子类中,函数事实上被定义为传递一个扩展Foo的类型” - 问题在于你的子类试图重新定义'distance()'只在一个特定类型的'Foo'上运行。 “Metric”合同声明任何'Foo'都可以传递给这个方法,而不仅仅是一种类型。要限制类的类型参数,请使用0verbose的方法,其中类型参数在类级声明。 – 2011-06-07 02:19:44

+0

非常好。即Metric metric = new MetricDefault(); metric.distance(new BizBat());'应该被期望成功,因为编译器不知道'metric'实际上是一个'MetricDefault',因此将接受参数类型'BizBat'(基于它的接受标准定义在'class Metric'中) - 但是当然'metric'真的*是一个'MetricDefault',所以(如果它编译的话,它不会)在_runtime_会有一个ClassCastException异常。另一方面,不可能写'BizBat bizbat = metric.precompute()' - 所以编译器很乐意让Bar作为返回类型。 – 2011-06-07 02:48:11

4

方法声明

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

意味着,该方法需要一个T参数,主叫方可以决定作为T使用哪个的Foo亚型。

方法声明

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

意味着该方法返回T类型的一些对象,主叫方可以决定作为T使用哪个的Foo亚型。

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

在你的具体类中,你尝试重写这些方法 - 为了我的理解你不应该被允许它们,但是编译器可能比我更放松一点。

我想你真正想要的是什么样的东西Overbose张贴。

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 {} 
} 
+0

非常有用的介绍什么擦除在这里做。谢谢。 – 2011-06-07 11:28:20

1

正如@保罗Ebermann提到的,压倒一切的以下应该是不合法

<T extends Foo> 
    T precompute(); 

    Bar precompute() 

规则JLSv3#8.4.8.3和#8.4 .5禁止这种压倒一切。 Javac应该报告错误。

有2条可能的解释

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

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

第一种解释很不合理。如果(鼻定义的)关系是可传递的,则很难检查它是否适用于2种任意类型。所以第二种解释可能更真实。

+0

我刚刚开始巩固我的理解,认为它*是合法的,因为它的构建没有错误。然而(正如我在@matt b的注释中所描述的那样),当你尝试用除显式的'Foo'之外的任何东西*调用函数'precompute'作为返回类型时,编译器*会提供一个错误(一个Bar失败)。也许这就解释了为什么上述建筑(似乎是合法的)? – 2011-06-07 12:03:39