2017-08-01 47 views
4

假设我们有三个类 - AbstractMessageAbstractEngineAbstractAction。这三个类都以通用的方式相互引用,因此每个引擎都有相应的消息和操作,您可以直接在代码中引用它们。如何确保这是我的类签名中引用的泛型类型?

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

而正是在这条线,它不太愿意 - 抱怨说:

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

现在其原因很明显 - 所讨论的E可能是一些OTHER AbstractEngine,并且无法知道我们调用它的子类是否实际上是一个E

我的问题是,我怎么能说对确定性,如果你要class MyEngine extends AbstractEngine<M...,E...,A...>MyEngineMUSTE?并有这种确定性烘烤到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!"); 
     } 
    } 

} 
+0

@Tezra并非所有的东西都需要mcve。我没有类型错误。对不起,你不明白这个问题,但也许你应该再读一遍。也许还可以读瓦伦丁的答案,这是对问题所在的尝试。 – corsiKa

+0

Tezra,A将永远是'AbstractAction' - 就在第一个代码片段中。就编译时限制它们的类型而言,我不想在每个类上都定义它们 - 我想在实例时声明它们。我以前使用过这个范例,并且它工作得很好*除了*这个愚蠢的类型问题不知道它们是在通用描述符中指定的类型。而已。 – corsiKa

+0

@Tezra对不起,对'AbstractMessage','AbstractAction'和'AbstractEngine'中的每一个,您都有'M extends AbstractMessage ,E extends AbstractEngine ,A extends AbstractAction 'rider。我认为这是这个问题的暗示,但它似乎没有。 – corsiKa

回答

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

使用类型安全的包装

另一种解决方案是创建一个绑定在一起这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不应该使用它们的基类互换。

只要使用多态和使用泛型只是作为一种分离器

下面是一个使用多态,而不是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!"); 
     } 
    } 

} 
+0

为了记录,正是这个最优雅地解决问题的选项6。 – corsiKa

+0

无关:你为我的问题提出的编辑 - 不太好。 A)我倾向于在放下版本1后快速编辑/更新我的答案 - 很多人都这样做。所以如果你想改进一个答案 - 等待几分钟,以确保你没有进入作者的方式,B)是的,我在那里的代码并不好 - 但我自己想到了。因此,请在删除答案中的内容时小心。 – GhostCat

+0

不过,我认为你的行为是最好的,我很感激! – GhostCat

2

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

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

明显的解决办法是增加一个铸((E)this),并且可能是一个可接受的解决方案,但你必须在合同(直通的javadoc或其他单证),其Engine扩展类必须分配E自己清楚。

另一种解决方案只是简单地将这些方法签名更改为更灵活一点,而不是E接受任何扩展为Engine<M, A, E>的引擎。

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

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

编辑

如果你想保持E类型参数applyTo另一种选择是创建一个字段中键入AbstractEngine E这将是一个传递给申请。实际上这个字段会参考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); 
    } 
} 

这部作品的原因是,当我们宣布MyEngine那么编译器不知道MyEngineE等等“投”是安全的。然后,AbstractEngine中的代码可以在此后安全地使用铸造值。

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

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

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

不幸的是,这不可能是检查在编译的时候......你可以做第二个最好的事情是让你的代码测试,以确认所有AbstractEngine扩展类符合,因此将在建设 - 部分失败时间。

+0

引擎不需要它自己的类型,但它确实需要知道消息属于THAT引擎,而不是其他类型的引擎。我试过其他的变化,最终总是需要所有三个三个。就像你说的,泛型总是麻烦。 – corsiKa

+0

至于你的解决方案,我现在正在铸造它。但我不想。我宁愿它按我想要的方式工作。至于你的第二种解决方案,更灵活的是与我想要的相反。我想要它僵化。 – corsiKa

+0

@corsika与THAT引擎,你的意思是引擎类型或实例?如果晚些时候你总是需要使用ref来引擎并在运行时进行验证。 –