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

时间:2017-08-01 17:37:11

标签: java generics

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

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

这工作正常,但当我尝试在最高级别强制执行时,我遇到了一些问题。我的AbstractAction类因此定义了applyTo方法:

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

我的AbstractEngine类有这个

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

正是在这条线上它bal然抱怨:

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

现在原因很明显 - 有问题的E可能是其他一些AbstractEngine,而且我们无法知道我们从中调用它的子类是否实际上是E

我的问题是,我怎么能肯定地说,如果你要class MyEngine extends AbstractEngine<M...,E...,A...> MyEngine 必须E?并将这个确定性纳入AbstractEngine

这是一个说明问题的小例子。

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 个答案:

答案 0 :(得分:3)

使用最松散的有效参数类型

最简单的解决方案是不实际执行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);
  }
}

使用类型安全的包装器

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

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不应该使用它们的基类互换。

只使用多态性并使用泛型作为类型分隔符

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

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

}

答案 1 :(得分:2)

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

这里的问题是矛盾的是,你无法保证this引用E的实例;关于this,我们唯一知道的是它也扩展了Engine<M, A, E>但实际上并不是E

明显的解决方案是添加一个强制转换((E)this),这可能是一个可接受的解决方案,但您必须在合同中明确(通过javadoc或其他文档)Engine扩展类必须分配{ {1}}对自己。

另一个解决方案只是将这些方法签名更改为更灵活,而E接受任何扩展E的引擎。

Engine<M, A, E>

还要考虑尽可能减少类型参数的数量。例如Engine是否需要引用自己的类型?它是否有任何接受或返回的方法和必须属于同一类型/类的引擎?

修改

如果要将protected abstract M applyTo(AbstractEngine<M, A, E> engine, Object guarantee); 类型参数保留在E中,则另一个选项是在AbstractEngine中创建一个类型为applyTo的字段,该字段将传递给apply to。事实上,这个字段会引用E,但一旦它被“铸造”#34;安全地施工。:

this

这样做的原因是,当我们声明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); } } 时,编译器确实知道MyEngineMyEngine,因此&#34; cast&#34;是安全的。然后E中的代码可以安全地使用铸造值。

明显的不便之处在于引用AbstractEngine的额外字段,虽然在实践中有点内存浪费可能是微不足道的。

在这里,我们增加了引擎可以指定在this方法调用中使用的代理引擎的可能性。也许这可能有用......但是如果你真的想让这里不可能使用第三个引擎,那么你可以改变apply构造函数中的代码来比较传递的引擎与AbstractEngine和如果它们不相同,则在运行时失败。

this

不幸的是,在编译时无法检查...你可以做的第二件事就是让它成为代码测试的一部分,以验证所有protected AbstractEngine(final E engine) { if (engine != this) { throw new IllegalArgumentException(); } this.engine = engine; } 扩展类是否合规,以便在构建时失败 - 时间。