代码之家  ›  专栏  ›  技术社区  ›  corsiKa

如何确保这是我的类签名中引用的泛型类型?

  •  4
  • corsiKa  · 技术社区  · 9 年前

    假设我们有三节课- AbstractMessage , AbstractEngine AbstractAction .这三个类都以通用方式相互引用,因此每个引擎都有相应的消息和操作,您可以在代码中直接引用它们。

    public class MyMessage<M extends AbstractMessage<M,E,A>, E extends AbstractEngine<M,E,A>, A extends AbstractAction<M,E,A>> {
    

    applyTo 由此定义的方法:

    protected abstract M applyTo(E engine, Object guarantee);
    

    private final M apply(A action) {
        return action.apply(this, this.guarantee);
    }
    

    The method applyTo(E, Object) in the type AbstractAction<M,E,A> is not
    applicable for the arguments (AbstractEngine<M,E,A>, Object)
    

    现在,原因很清楚了——所讨论的E可能是其他一些抽象引擎,并且没有办法知道我们从中调用它的子类是否实际上是一个 E

    我的问题是,如果你要 class MyEngine extends AbstractEngine<M...,E...,A...> MyEngine 成为一名 ? 并将这种确定性融入 抽象引擎 ?

    class EngineExample {
    
        static abstract class AbEng<A extends AbAct<A,M,E>, M extends AbMes<A,M,E>, E extends AbEng<A,M,E>> {
    
            final M func(A act) {
                return act.apply(this); // compile error here
            }
    
        }
    
        static abstract class AbMes<A extends AbAct<A,M,E>, M extends AbMes<A,M,E>, E extends AbEng<A,M,E>> {
    
        }
    
        static abstract class AbAct<A extends AbAct<A,M,E>, M extends AbMes<A,M,E>, E extends AbEng<A,M,E>> {
    
            abstract void apply(E e);
    
        }
    
        static class RealEng extends AbEng<RealAct, RealMes, RealEng> {
    
        }
    
        static class RealMes extends AbMes<RealAct, RealMes, RealEng> {
    
        }
    
        static class RealAct extends AbAct<RealAct, RealMes, RealEng> {
    
            void apply(RealEng eng) {
                System.out.println("applied!");
            }
        }
    
    }
    
    2 回复  |  直到 9 年前
        1
  •  3
  •   Tezra    9 年前

    最简单的解决方案是不强制执行 this isInstanceOf E

    abstract Action<E> {
      public void apply(Engine<?> e, Object o) {
        e.doSomething(o);
      }
    }
    

    abstract Action<E> {
      <T extends Engine<?>> public T apply(T e, Object o) {
        return e.doSomething(o);
      }
    }
    

    另一种解决方案是创建另一个类,将这3个类绑定在一起,并将交互调用移动到包装器。

    abstract System<A extends Action, M extends Message, E extends Engine> {
        abstract void apply(A action, E engine) {
            engine.render(action.apply())
        }
    }
    

    或者让包装类获取这3个实例,并使用传入的版本。这基本上是“允许任何足够近的东西”的解决方案,并添加了另一个类来管理它们如何能够和不能相互对话。

    预制检查

    如果强制转换设置无效,也可以对构造进行引用强制转换以引发错误。

    private final E dis = (E) this;
    


    下一个解决方案有点特定于您的案例(使用我们讨论中的信息)。基本上,您希望在抽象类中定义一个类a和B可以继承的方法,但a和B不应使用其基类进行互换。

    只需使用polymophism,并将泛型用作类型分离器

    这里是MVCe的一个修改,它使用多态性,仅将泛型用作一种类型-类别-独占锁定机制。基本上,类型是一个语义接口,表示这些类是否在语义上相互对话是有意义的。(物理引擎和光引擎可能共享一些功能,但让它们互换是没有意义的。)

    class test {
    
        public static void main(String[] rawrs) {
            RealEng re = new RealEng();
            RealAct ra = new RealAct();
            MockAct ma = new MockAct();
            ra.apply(re);
            // Remove all code related to Type interface if next line should compile
            ma.apply(re); // compile error here
        }
    
        static interface Type {
        }
    
        static interface Real extends Type {
        };
    
        static interface Mock extends Type {
        };
    
        static abstract class AbEng<T extends Type> {
    
            final void func(AbAct<T> act) {
                act.apply(this); // compile error here
            }
    
        }
    
        static abstract class AbMes<T extends Type> {
    
        }
    
        static abstract class AbAct<T extends Type> {
    
            abstract void apply(AbEng<T> e);
    
        }
    
        static class RealEng extends AbEng<Real> {
    
        }
    
        static class RealMes extends AbMes<Real> {
    
        }
    
        static class RealAct extends AbAct<Real> {
            @Override
            void apply(AbEng<Real> eng) {
                System.out.println("applied!");
            }
        }
    
        static class MockAct extends AbAct<Mock> {
            @Override
            void apply(AbEng<Mock> eng) {
                System.out.println("applied!");
            }
        }
    
    }
    
        2
  •  2
  •   Valentin Ruano    9 年前

    Java泛型中的递归类型参数通常很麻烦。

    这里的问题是,矛盾的是,你无法保证这一点 this E ; 我们唯一知道的事 它还扩展了 Engine<M, A, E>

    显而易见的解决方案是添加一个cast( (E)this Engine E

    另一种解决方案只是将这些方法签名更改为更灵活一点,而不是 E .

    protected abstract M applyTo(AbstractEngine<M, A, E> engine, Object guarantee);
    

    编辑

    如果你想保留 applyTo 另一种选择是创建一个类型为 但一旦它在施工中安全“浇铸”:

    public class AbstractEngine<M extends ..., A extends ..., E extends ...> {
        private final E engine;
    
        protected AbstractEngine(final E engine) {
           this.engine = Objects.requiresNonNull(engine);
        }
    }
    
    public class MyEngine extends AbstractEngine<MyMessage, MyAction, MyEngine> {
        public MyEngine() {
           super(this);
        }
    }
    

    MyEngine 那么编译器就知道了 MyEngine公司 所以“演员阵容”是安全的。然后输入代码 AbstractEngine 之后可以安全地使用铸造值。

    明显的不便之处在于参考的额外字段 这虽然是一点内存浪费,但在实践中可能可以忽略不计。

    在这里,我们添加了一种可能性,即引擎可以指定一个代理引擎用于其 apply 方法调用。也许这很有用……但是如果你真的想让第三个引擎无法在这里使用,那么你可以在 抽象引擎 如果它们不相同,则在运行时失败。

    protected AbstractEngine(final E engine) {
       if (engine != this) {
          throw new IllegalArgumentException();
       }
       this.engine = engine;
    }
    

    不幸的是,这无法在编译时进行检查……您可以做的第二件事是将其作为代码测试的一部分,以验证所有 扩展类是兼容的,因此在构建时会失败。