开发者

Why is a generic argument that "extends" something not allowed in derived function, but generic return type is?

开发者 https://www.devze.com 2023-03-10 16:45 出处:网络
I would like to override a generic function in a subclass, as follows: Superclass: public abstract class Metric {

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

  1. the spec omitted an intuitive&obvious rule: the "is return-type-substitutable" relation should be transitive. T->Foo->Bar, Bar overriding T is legal.

  2. 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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号