简体   繁体   English

Java泛型方法擦除和继承

[英]Java generic method erasure and inheritance

I am running into a problem with javas generics and overriding methods.我遇到了 javas 泛型和覆盖方法的问题。 Imagine I have a deep tree-like class hierarchy.想象一下,我有一个很深的树状类层次结构。 The top-level class defines a method foo which takes 1 argument of type Strategy.顶级类定义了一个方法 foo,它接受 1 个 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.我的类层次结构中的每个类都需要覆盖 foo 以限制它可以传递的 Strategy 类型,以便策略的泛型类型参数与声明类匹配。 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.在此示例中,我希望能够使用分别在 TopLevelClass、MidLevelClass 和 LowLevelClass 中定义的任何静态引用 s1、s2 和 s3 对类 LowLevelClass 的实例调用 foo。 Ideally I would not have to call different methods foo1, foo2 or foo3 depending on the argument.理想情况下,我不必根据参数调用不同的方法 foo1、foo2 或 foo3。

The code above does NOT compile in java.上面的代码不能在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名称冲突:MidLevelClass 类型的方法 foo(Strategy) 与 TopLevelClass 类型的 foo(Strategy) 具有相同的擦除但不覆盖它

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.必须可以在对象的生命周期内多次调用 foo。

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我在这里打开了一个更详细的问题来解释我的问题的背景: 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; Strategy<LowLevelClass>的实例不可能是Strategy<MidLevelClass> ,它不可能是 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.) bar(TopLevelClass)不能覆盖bar(MidLevelClass)也一直是正确的,尽管从 1.5 开始就有协变返回类型。)

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作为参数添加到Strategy.foo的调用中。 This implies MidLevelClass must be abstract - it cannot guarantee foo is overridden.这意味着MidLevelClass必须是abstract ——它不能保证foo被覆盖。 The type of this now needs to fit the type parameter. this的类型现在需要适合类型参数。 To do that, add an abstract "getThis" method (concrete in concrete subclasses).为此,添加一个abstract “getThis”方法(具体子类中的具体方法)。

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

The type of the static fields requires wildcards: static字段的类型需要通配符:

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.由于您的每个类仅使用同一类中的方法实现自己的Strategy ,因此Strategy.bar()不需要获取该类的实例。 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.由于foo()所有实现都在做完全相同的事情,因此您不需要多个实现。

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.部分原因是,在一个好的解决方案中,您应该能够在更改后的foo()方法中传递TopLevelClass的引用。 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".我已经评论了以“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 );
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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