I would like to override a generic function in a subclass, as follows:
Superclass:
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 {}
}
Subclass:
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 {}
}
The g开发者_StackOverflow中文版eneric function, when the generic type is the return value of the function, is valid Java code and builds successfully.
Changing only the placement of the generic type - making it the argument to the function, rather than the return type - and it becomes invalid Java code.
Why is the latter case invalid? What can I do to implement this functionality?
The method declaration
public abstract <T extends Foo> void distance(T arg);
means that the method takes an T
argument, and the caller can decide which subtype of Foo
to use as T
.
The method declaration
public abstract <T extends Foo> T precompute();
means that the method returns some object of type T
, and the caller can decide which subtype of Foo
to use as T
.
This last method can't really be implemented (apart from returning null
), since your method does not really know what an object to produce here.
In your concrete class you try to override the methods - for my understanding you shouldn't be allowed for both of them, but maybe the compiler is a bit more relaxed than me.
I think what you really want is something like what Overbose posted.
public abstract class Metric<T extends Foo> {
public abstract T precompute(); // valid syntax
public abstract void distance(T arg); // valid syntax
public class Foo {}
}
then if as I understood Bar extends 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 {}
}
Because the signature of distance(T)
in the superclass specifies that someone that has a reference to a Metric
should be allowed to pass any type that extends Foo
as an argument.
The first method works fine because anyone referring to Metric
is already prepared to handle the return type of Bar
, since the signature refers to a parent type of Bar
.
For this to work, you need to make T
a parameter of the type, rather than of the methods.
The way you are attempting to do it, your new method does not override the abstract base method. After type erasure is performed, it amounts to the following code:
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 {}
}
As @Paŭlo Ebermann mentioned, the following overriding should not be legal
<T extends Foo>
T precompute();
Bar precompute()
Rules in JLSv3 #8.4.8.3 and #8.4.5 forbid this overriding. Javac should have reported error on it.
There are 2 possible explanations
the spec omitted an intuitive&obvious rule: the "is return-type-substitutable" relation should be transitive.
T->Foo->Bar
,Bar
overridingT
is legal.javac is buggy. It somehow mistakes
Foo
as the return type of method#1 when validating the overriding.
The 1st explanation is quite implausible. If the (nastily defined) relation is transitive, it's too hard to check if it holds for 2 arbitrary types. So the 2nd explanation is probably truer.
精彩评论