【问题标题】:Java generic method erasure and inheritanceJava泛型方法擦除和继承
【发布时间】:2020-02-02 12:53:11
【问题描述】:

我遇到了 javas 泛型和覆盖方法的问题。想象一下,我有一个很深的树状类层次结构。顶级类定义了一个方法 foo,它接受 1 个 Strategy 类型的参数。策略有一个泛型类型参数。

我的类层次结构中的每个类都需要重写 foo 以限制它可以传递的策略类型,以便策略的泛型类型参数与声明类匹配。下面是一个例子:

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);
}

在此示例中,我希望能够在具有分别在 TopLevelClass、MidLevelClass 和 LowLevelClass 中定义的任何静态引用 s1、s2 和 s3 的类 LowLevelClass 的实例上调用 foo。理想情况下,我不必根据参数调用不同的方法 foo1、foo2 或 foo3。

上面的代码在java中编译。编译时错误是:

名称冲突:MidLevelClass 类型的方法 foo(Strategy) 与 TopLevelClass 类型的 foo(Strategy) 具有相同的擦除,但不会覆盖它

我怀疑这很容易解决。我可以只使用原始类型并依赖运行时类型检查,但我宁愿保持类型安全。在不牺牲类型层次结构或类型安全的情况下,我能做些什么来实现这一点?请注意,在构造函数中传递策略不是对我来说是一个选项!在对象的生命周期内必须可以多次调用 foo。

编辑:

我意识到,如果不了解周围的情况,这个问题可能很难理解。我在这里打开了一个更详细的问题来解释我的问题的背景:How to make this Strategy-Object pattern type safe

【问题讨论】:

  • 我认为你过度封装Strategy;因为现在你的策略与你调用它的对象特别相关。您只需将调用该方法的对象传回即可。到时候简直是浪费。你想用实际的背景问题来实现什么?
  • 我将把它用于高级事件 -> UI 框架的动作系统。策略接口是响应键输入、鼠标移动、拖放、复制和粘贴等事件所采取的操作的替代。例如,TopLevelClass 将是某种抽象小部件。 MidLevelClass 将是一个具有复制和选择操作的集合组件。 LowLevelClass 将是一个带有箭头键移动操作的列表。

标签: java generics inheritance type-safety


【解决方案1】:

如果您担心擦除,那么只需为单独的方法使用单独的方法名称:

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) {/*...*/}
}

但是,我怀疑擦除不是您的问题。您可能想要覆盖相同的方法。

Strategy&lt;LowLevelClass&gt; 的实例不可能是 Strategy&lt;MidLevelClass&gt;,它不可能是策略;

给定

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

那么你不能将一个分配给另一个。

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

因此,通过方法覆盖来做同样的事情是没有意义的。 (bar(TopLevelClass) 不能覆盖 bar(MidLevelClass) 也是事实,尽管从 1.5 开始就有协变返回类型。)

在类中添加类型参数以用作方法中的类型参数。

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) {/*...*/}
}

更新后的问题添加了this 的使用作为Strategy.foo 调用的参数。这意味着 MidLevelClass 必须是 abstract - 它不能保证 foo 被覆盖。 this 的类型现在需要适合类型参数。为此,添加一个abstract "getThis" 方法(具体在具体子类中)。

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

static 字段的类型需要通配符:

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

(更好的设计更喜欢组合而不是继承。)

【讨论】:

  • 感谢您的回答。不幸的是,我意识到我的问题不够详细。请查看我对问题所做的修改,了解为什么您的解决方案不适用于我的情况。
  • 感谢您再次回复此问题。我之前考虑过您的解决方案,但发现它不足以满足我的需求。主要原因是泛型类型参数的声明变得多么大。在实践中,这通常会导致用户为了简洁而牺牲类型安全。请在此处查看我的扩展问题:stackoverflow.com/questions/60028518/…
【解决方案2】:

我觉得这个设计有几个困难:

  1. 由于您的每个类仅使用同一类中的方法实现其自己的Strategy,因此Strategy.bar() 无需获取该类的实例。参数的这种传递是阻碍其巧妙实现的原因之一。
  2. 由于foo() 的所有实现都在做完全相同的事情,因此您不需要多个实现。

这是一个有部分解决方案的代码。部分原因是,在一个好的解决方案中,您应该能够在更改后的 foo() 方法中传递 TopLevelClass 的引用。如果你能想出一个办法,我认为它会很棒。使用此解决方案,类层次结构不是一个因素,因为我们使用的是特定的引用类型。

我已经评论了以“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 );
    }
}

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2011-03-06
  • 1970-01-01
  • 1970-01-01
  • 2011-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多