简体   繁体   中英

Java generic method erasure and inheritance

I am running into a problem with javas generics and overriding methods. Imagine I have a deep tree-like class hierarchy. The top-level class defines a method foo which takes 1 argument of type Strategy. Strategy has a generic type parameter.

Each class in my class hierarchy needs to override foo to limit the kind of Strategy it can be passed so that the generic type parameter of the strategy matches the declaring class. Below is an example:

abstract static class TopLevelClass {

    static final Strategy<TopLevelClass> s1 = tlc -> System.out.println(tlc.getTopLevelAtt());

    String getTopLevelAtt() {
        return "TopLevelAtt";
    }

    void foo(Strategy<TopLevelClass> s) {s.bar(this);}
}
static class MidLevelClass extends TopLevelClass {

    static final Strategy<MidLevelClass> s2 = mlc -> System.out.println(mlc.getMidLevelAtt());

    String getMidLevelAtt() {
        return "MidLevelAtt";
    }

    void foo(Strategy<MidLevelClass> s) {s.bar(this);}
}
static class LowLevelClass extends MidLevelClass  {

    static final Strategy<LowLevelClass> s3 = llc -> System.out.println(llc.getTopLevelAtt());

    String getLowLevelAtt() {
        return "LowLevelAtt";
    }

    void foo(Strategy<LowLevelClass> s) {s.bar(this);}
}
static interface Strategy<X> {
    void bar(X x);
}

In this example I want to be able to call foo on instances of class LowLevelClass with any of the static references s1, s2 and s3 defined in TopLevelClass, MidLevelClass and LowLevelClass respectively. Ideally I would not have to call different methods foo1, foo2 or foo3 depending on the argument.

The code above does NOT compile in java. The compile-time-error is:

Name clash: The method foo(Strategy) of type MidLevelClass has the same erasure as foo(Strategy) of type TopLevelClass but does not override it

I doubt this can easily be resolved. I could just use raw-types and rely on run-time typechecks but I would rather keep type safety. What can I do to achieve this without sacrificing the type hierarchy or type safety? Please note that passing the Strategy in the constructor IS NOT an option for me! It must be possible to call foo multiple times over the life time of the object.

Edit:

I realize, that this problem is perhaps difficult to follow without knowing the circumstances surrounding it. I have opened a more detailed question explaining the background of my problem here: How to make this Strategy-Object pattern type safe

If you are worried about erasure then just use separate method names for separate methods:

abstract class TopLevelClass {
    void fooTop(Strategy<TopLevelClass> s) {/*...*/}
}
class MidLevelClass extends TopLevelClass {
    void fooMid(Strategy<MidLevelClass> s) {/*...*/}
}
class LowLevelClass extends MidLevelClass  {
    void fooLow(Strategy<LowLevelClass> s) {/*...*/}
}

However, I suspect erasure is not your problem . You presumably want to override the same method.

An instance of Strategy<LowLevelClass> cannot possibly be a Strategy<MidLevelClass> , which cannot be a Strategy;

Given

Strategy<LowLevelClass> l;
Strategy<MidLevelClass> m;

Then you cannot assign one to another.

l = m; // Compile-time fail.
m = l; // Compile-time fail.

And therefore it would make no sense to be able to do the same via method overriding. (It's also always been true that bar(TopLevelClass) cannot override bar(MidLevelClass) , though since 1.5 there are covariant return types.)

Add a type parameter to the class to use as a type argument in the method.

abstract class TopLevelClass<T extends TopLevelClass<T>> {
    void foo(Strategy<T> s) {/*...*/}
}
class MidLevelClass<T extends MidLevelClass<T>> extends TopLevelClass<T> {
    void foo(Strategy<T> s) {/*...*/}
}
class LowLevelClass<T extends LowLevelClass<T>> extends MidLevelClass<T>  {
    void foo(Strategy<T> s) {/*...*/}
}

The updated question add the use of this as a argument to a call of Strategy.foo . This implies MidLevelClass must be abstract - it cannot guarantee foo is overridden. The type of this now needs to fit the type parameter. To do that, add an abstract "getThis" method (concrete in concrete subclasses).

    protected abstract X getThis();
...
    @Override protected X getThis() { return this; }

The type of the static fields requires wildcards:

static final Strategy<? extends TopLevelClass<?>> s1 =
    tlc -> System.out.println(tlc.getTopLevelAtt());

(Better designs prefer composition over inheritance.)

I feel that there a couple of difficulties with this design:

  1. Since each of your class implements its own Strategy using only methods within the same class, there is no need for Strategy.bar() to take an instance of the class. This passing of parameter is one of the causes of the hindrance to implementing this neatly.
  2. Since all the implementations of foo() are doing exactly the same thing, you don't need multiple implementations.

Here is a code that has a partial solution. Partial because, in a good solution, you should be able to pass the reference of TopLevelClass in the changed foo() method. If you can figure out a way for that, I think it will be just great. With this solution, the class hierarchy is not a factor since we are using specific reference types.

I have commented the changed parts starting "CHANGE".

public class Erasure1{

    public static void main( String[] args ){
        LowLevelClass slc = new LowLevelClass(); //'slc' must be exactly the same type as the instance. This is a limitation with this solution. Ideal thing would have been that this reference be of type TopLevelClass.
        slc.foo( LowLevelClass.s3, slc );
    }

    abstract static class TopLevelClass{

        static final Strategy<TopLevelClass> s1 = tlc -> System.out.println( tlc.getTopLevelAtt() );

        String getTopLevelAtt(){ return "TopLevelAtt"; }

        /* CHANGE 1: It is necessary that the instance of TopLevelClass subtype be passed since 
         * Strategy.bar() doesn't accept wildcards. Changing it to accept a 'T' to pass to bar().
         * Also, since, this is now taking 'T' as a parameter, this method could very well be a 
         * static method in a utility class. */
        <T> void foo( Strategy<T> s, T tlc ){
            s.bar( tlc );
        }
    }

    static class MidLevelClass extends TopLevelClass{
        static final Strategy<MidLevelClass> s2 = mlc -> System.out.println(mlc.getMidLevelAtt());;

        String getMidLevelAtt(){ return "MidLevelAtt"; }

        /* CHANGE 2: Since this method is not doing anything different from its parent, this is not required. */
        //void foo(Strategy<MidLevelClass> s) {s.bar(this);}
    }

    static class LowLevelClass extends MidLevelClass{

        static final Strategy<LowLevelClass> s3 = llc -> System.out.println( llc.getLowLevelAtt() );

        String getLowLevelAtt(){ return "LowLevelAtt"; }

        /* CHANGE 2: Since this method is not doing anything different from its parent, this is not required. */
        //void foo(Strategy<LowLevelClass> s) {s.bar(this);}
    }

    static interface Strategy<X> {
        void bar( X x );
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM