Reputation: 82559
Suppose we have three classes - AbstractMessage
, AbstractEngine
, and AbstractAction
. These three classes all reference each other in a generic fashion so each Engine has a corresponding message and action and you can refer to them directly in the code.
public class MyMessage<M extends AbstractMessage<M,E,A>, E extends AbstractEngine<M,E,A>, A extends AbstractAction<M,E,A>> {
This is working fine, but when I attempt to enforce behavior at the highest level I run into some problems. My AbstractAction class has an applyTo
method defined thusly:
protected abstract M applyTo(E engine, Object guarantee);
and my AbstractEngine class has this
private final M apply(A action) {
return action.apply(this, this.guarantee);
}
And it is on this line that it balks - complaining that:
The method applyTo(E, Object) in the type AbstractAction<M,E,A> is not
applicable for the arguments (AbstractEngine<M,E,A>, Object)
Now the reason for this is clear - the E in question might be some OTHER AbstractEngine and there's no way to know if the subclass we're calling this from is actually an E
.
My question is, how can I say for certainty that if you're going to class MyEngine extends AbstractEngine<M...,E...,A...>
that MyEngine
is MUST be an E
? And to have this certainty baked into AbstractEngine
?
Here is a small example that illustrates the problem.
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!");
}
}
}
Upvotes: 4
Views: 334
Reputation: 8833
The simplest solution, is to not actually enforce that this isInstanceOf E
. The rules of abstraction already guarantee that this is a safe operation, so it will just work if you change the parameter to just allow any Engine.
abstract Action<E> {
public void apply(Engine<?> e, Object o) {
e.doSomething(o);
}
}
or
abstract Action<E> {
<T extends Engine<?>> public T apply(T e, Object o) {
return e.doSomething(o);
}
}
Another solution is to create another class that binds these 3 together, and move the interaction calls to the wrapper.
abstract System<A extends Action, M extends Message, E extends Engine> {
abstract void apply(A action, E engine) {
engine.render(action.apply())
}
}
Or have the wrapper class take an instance of these 3 and use the passed in versions. This basically is the "allow anything close enough" solution, and adding another class to manage how they can and can't talk to each other.
You can also make a reference cast on construction to throw an error if the cast setup is invalid.
private final E dis = (E) this;
This really just moves the problem from from always on compile time, to sometimes on run time, so in general, not a safe/stable solution.
This next solution is a bit specific to your case (using info from our discussion). Basically, you want to define a method in an abstract class that class A and B can inherit, but A and B should not be interchangeable using their base class.
Here is a modification of the MVCe that uses polymorphism instead, using generics only as a kind of type-category-exclusive-locking mechanism. Basically, Type is a semantic interface to say whether semantically, it makes sense for these classes to talk to each other. (A Physics Engine and Light Engine may share some functionality, but it would make no sense to let them be interchangeable.)
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!");
}
}
}
Upvotes: 3
Reputation: 2809
Recursive type-parameters in Java generics are often troublesome.
The problem here is that paradoxically there in no way that you can guaranteed that this
makes reference to an instance of E
; the only thing that we known about this
is that it also extends Engine<M, A, E>
but not that is actually E
.
Obvious solution is to add a cast ((E)this
) and that might be an acceptable solution but you must make clear in the contract (thru javadoc or other documentations) that Engine
extending classes must assign E
to themselves.
Another solution simply change those method signatures to be a bit more flexible and instead E
accept any engine that extends Engine<M, A, E>
.
protected abstract M applyTo(AbstractEngine<M, A, E> engine, Object guarantee);
Also consider to reduce the number of type-parameters whenever possible. For example does Engine need to make reference to its own type? Does it have any method that accepts or returns and engine that must be of the same type/class?
EDIT
If you want to keep the E
type parameter in applyTo
another option is to create a field typed E
in AbstractEngine that would be the one passed to the apply to. This field in fact would make reference to this
but once it has been "casted" safely at construction.:
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);
}
}
The reason this works is that when we are declaring MyEngine
then the compiler does know that MyEngine
is E
and so the "cast" is safe one. Then the code in AbstractEngine
can use the casted value safely thereafter.
The obvious inconvenience is the extra field making reference to this
, that although is a bit of memory waste in practice is probably negligible.
Here we are adding the possibility that an engine could designate a surrogate engine to be used in their apply
method calls. Perhaps that might be useful... but if you really want to make it impossible to use a third engine here, then you can change the code in the AbstractEngine
constructor to compare the passed engine with this
and fail at runtime if they are not the same.
protected AbstractEngine(final E engine) {
if (engine != this) {
throw new IllegalArgumentException();
}
this.engine = engine;
}
Unfortunately this cannot be check at compilation time... the second best thing you can do is to make it part of your code tests to verify that all AbstractEngine
extending classes are compliant so it would fail at build-time.
Upvotes: 2