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

Java 如何确保这是类签名中引用的泛型类型?,java,generics,Java,Generics,假设我们有三个类-AbstractMessage,AbstractEngine,和AbstractAction。这三个类都以通用方式相互引用,因此每个引擎都有相应的消息和操作,您可以在代码中直接引用它们 public class MyMessage<M extends AbstractMessage<M,E,A>, E extends AbstractEngine<M,E,A>, A extends AbstractAction<M,E,A>> {

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

public class MyMessage<M extends AbstractMessage<M,E,A>, E extends AbstractEngine<M,E,A>, A extends AbstractAction<M,E,A>> {
我的AbstractEngine类有这个

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)

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

这里的问题是,矛盾的是,你无法保证
这个
引用了
E
的一个实例;关于
这个
我们知道的唯一一件事是它还扩展了
引擎
,但实际上它不是
E

显而易见的解决方案是添加一个cast(
(E)this
),这可能是一个可接受的解决方案,但您必须在合同(通过javadoc或其他文档)中明确指出,
引擎
扩展类必须将
E
分配给它们自己

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

这样做的原因是,当我们声明
MyEngine
时,编译器确实知道
MyEngine
E
,因此“cast”是安全的。然后,
AbstractEngine
中的代码可以安全地使用铸造值

明显的不便之处在于额外的字段引用了
这个
,虽然在实践中有一点内存浪费,但可能可以忽略不计

在这里,我们添加了一种可能性,即引擎可以指定一个代理引擎,用于它们的
apply
方法调用。也许这会有用。。。但是如果您真的想让第三个引擎无法在这里使用,那么您可以在
AbstractEngine
构造函数中更改代码,将传递的引擎与
this
进行比较,如果它们不相同,则在运行时失败

protected AbstractEngine(final E engine) {
   if (engine != this) {
      throw new IllegalArgumentException();
   }
   this.engine = engine;
}
不幸的是,这不能在编译时检查。。。您可以做的第二件最好的事情是将其作为代码测试的一部分,以验证所有
AbstractEngine
扩展类是否兼容,以便在构建时失败。

使用最宽松的有效参数类型 最简单的解决方案是,不要强制执行
这是E的替代。抽象规则已经保证了这是一个安全的操作,因此,如果您将参数更改为只允许任何引擎,那么它将正常工作

abstract Action<E> {
  public void apply(Engine<?> e, Object o) {
    e.doSomething(o);
  }
}
这实际上只是将问题从总是在编译时转移到有时在运行时,因此通常不是一个安全/稳定的解决方案


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

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

类测试{
公共静态void main(字符串[]rawrs){
RealEng re=新RealEng();
RealAct ra=新RealAct();
MockAct ma=新MockAct();
ra.申请(再申请);
//若下一行需要编译,请删除所有与类型接口相关的代码
ma.apply(re);//此处编译错误
}
静态接口类型{
}
静态接口实扩展类型{
};
静态接口模拟扩展类型{
};
静态抽象类标记{
最终无效职能(AbAct法案){
act.apply(this);//此处编译错误
}
}
静态抽象类AbMes{
}
静态抽象类AbAct{
摘要无效申请(AbEng e);
}
静态类RealEng扩展了AbEng{
}
静态类领域扩展了AbMes{
}
静态类RealAct扩展了AbAct{
@凌驾
无效申请(阿本英语){
System.out.println(“应用!”);
}
}
静态类MockAct扩展了AbAct{
@凌驾
无效申请(阿本英语){
System.out.println(“应用!”);
}
}
}

引擎本身不需要自己的类型,但它需要知道消息属于该引擎,而不是其他引擎。我尝试过其他的变化,最终总是需要所有三对所有三。就像你说的,泛型总是麻烦。至于你的解决方案,我目前正在铸造它。但我宁愿不去。我宁愿它按我想要的方式工作。至于你的第二个解决方案,更加灵活与我所希望的正好相反。我想要它和那个引擎一起,你是指引擎类型还是实例?如果是后者,则始终需要使用引擎的引用,并在运行时进行验证。有趣的一点。当我发表评论时,我的本意是前者。但是我想得越多,我就越意识到,因为这些引擎不是单例(在一台服务器上肯定会有多个相同引擎类型的实例运行),所以我必须考虑这个问题的答案。尽管如此,我还是觉得这个
(E)的演员阵容有点老套,我想避免。不过,您的问题很重要,可能会使事情复杂化。@corsika要使E保留在applyTo中并避免每次强制转换,您可以做的是在AbstrsctEngine中声明一个字段,该字段由构造函数设置为.final E,该构造函数将is值作为参数,具体引擎将自己传递给该构造函数。将上升
protected abstract M applyTo(AbstractEngine<M, A, E> engine, Object guarantee);
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);
    }
}
protected AbstractEngine(final E engine) {
   if (engine != this) {
      throw new IllegalArgumentException();
   }
   this.engine = engine;
}
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())
    }
}
private final E dis = (E) this;
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!");
        }
    }

}