Suppose I have 2 classes called RealNumber
and IntNumber
, and a method called plus
:
public class RealNumber<S extends RealNumber> {
public S plus(S realNumber) {
...
}
}
public class IntNumber
extends RealNumber<IntNumber> { }
When using the plus
method I get some compiler warnings and errors:
RealNumber x = new RealNumber();
IntNumber y = new IntNumber();
RealNumber sum1 = x.plus(x); // Warning: Unchecked call to 'plus(S)' as a member of raw type 'RealNumber'.
RealNumber sum2 = x.plus(y); // Warning: Unchecked call to 'plus(S)' as a member of raw type 'RealNumber'.
RealNumber sum3 = y.plus(x); // Error: plus (IntNumber) in RealNumber cannot be applied to (RealNumber).
RealNumber sum4 = y.plus(y); // This works fine.
IntNumber sum5 = y.plus(x); // Error: plus (IntNumber) in RealNumber cannot be applied to (RealNumber).
IntNumber sum6 = y.plus(y); // This works fine.
To fix this, I modify the plus
method, as follows:
public <T extends RealNumber> S plus(T realNumber) {
...
}
And now everything works fine. Good!
However, I want now to add a third class called PositiveIntNumber
that extends IntNumber
:
public class PositiveIntNumber extends IntNumber { }
This, of course, does not work:
RealNumber x = new RealNumber();
IntNumber y = new IntNumber();
PositiveIntNumber z = new PositiveIntNumber();
RealNumber sum1 = x.plus(x); // Fine.
RealNumber sum2 = x.plus(y); // Fine.
RealNumber sum3 = y.plus(x); // Fine.
RealNumber sum4 = y.plus(y); // Fine.
IntNumber sum5 = y.plus(x); // Fine.
IntNumber sum6 = y.plus(y); // Fine.
RealNumber sum7 = x.plus(z); // Fine.
IntNumber sum8 = y.plus(z); // Fine.
PositiveIntNumber sum9 = z.plus(x); // Error: incompatible types: no instance(s) of type variable(s) T exist so that IntNumber conforms to PositiveIntNumber
PositiveIntNumber sum10 = z.plus(y); // Error: incompatible types: no instance(s) of type variable(s) T exist so that IntNumber conforms to PositiveIntNumber
PositiveIntNumber sum11 = z.plus(z); // Error: incompatible types: no instance(s) of type variable(s) T exist so that IntNumber conforms to PositiveIntNumber
To fix this again, I modify class definitions, as follows:
public class IntNumber<S extends IntNumber>
extends RealNumber<S> { }
public class PositiveIntNumber
extends IntNumber<PositiveIntNumber>
{ }
This solves the problem for PositiveIntNumber
, but breaks IntNumber
:
RealNumber x = new RealNumber();
IntNumber y = new IntNumber();
PositiveIntNumber z = new PositiveIntNumber();
RealNumber sum1 = x.plus(x); // Fine.
RealNumber sum2 = x.plus(y); // Fine.
RealNumber sum3 = y.plus(x); // Fine.
RealNumber sum4 = y.plus(y); // Fine.
IntNumber sum5 = y.plus(x); // Error: incompatible types: RealNumber cannot be converted to IntNumber.
IntNumber sum6 = y.plus(y); // Error: incompatible types: RealNumber cannot be converted to IntNumber.
RealNumber sum7 = x.plus(z); // Fine.
IntNumber sum8 = y.plus(z); // Error: incompatible types: RealNumber cannot be converted to IntNumber.
PositiveIntNumber sum9 = z.plus(x); // Fine.
PositiveIntNumber sum10 = z.plus(y); // Fine.
PositiveIntNumber sum11 = z.plus(z); // Fine.
A cast fixes it, but it should be unnecessary, in my view:
IntNumber sum5 = (IntNumber)y.plus(x);
So, I have two questions:
1) Since y
is an IntNumber
, and the return type S
extends IntNumber
, why does y.plus(...) return RealNumber
?
2) How to fix this?
Edit: Writing RealNumber<RealNumber>
should be unnecessary, since RealNumber<IntNumber>
and RealNumber<PositiveIntNumber>
make absolutely no sense. So maybe the entire use of generics like this is just plain wrong. I think this answers question 1: y.plus(...)
returns RealNumber
because y is raw, so Java type system just doesn't care anymore that S
extends IntNumber
. However, I want to avoid repeating the plus
method in all subclasses of RealNumber, and I should be able to use generics, in some way, to avoid that. So question 2 still stands. What should I be doing?
Note: My actual classes are not Reals and Integers, but some other complicated business classes. Think of them as classes A, B, C and don't question the model. They are "addables", yes, but here X.plus(Y) should return type X, for every X and Y.