假设我们有三个类 - AbstractMessage
,AbstractEngine
和AbstractAction
。这三个类都以通用方式相互引用,因此每个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!");
}
}
}
答案 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);
}
}
时,编译器确实知道MyEngine
是MyEngine
,因此&#34; cast&#34;是安全的。然后E
中的代码可以安全地使用铸造值。
明显的不便之处在于引用AbstractEngine
的额外字段,虽然在实践中有点内存浪费可能是微不足道的。
在这里,我们增加了引擎可以指定在this
方法调用中使用的代理引擎的可能性。也许这可能有用......但是如果你真的想让这里不可能使用第三个引擎,那么你可以改变apply
构造函数中的代码来比较传递的引擎与AbstractEngine
和如果它们不相同,则在运行时失败。
this
不幸的是,在编译时无法检查...你可以做的第二件事就是让它成为代码测试的一部分,以验证所有protected AbstractEngine(final E engine) {
if (engine != this) {
throw new IllegalArgumentException();
}
this.engine = engine;
}
扩展类是否合规,以便在构建时失败 - 时间。