3

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.

Marcelo Glasberg
  • 29,013
  • 23
  • 109
  • 133
  • 4
    For one, `RealNumber` is generic. Parameterize its uses. Don't use raw types. – Sotirios Delimanolis Mar 20 '15 at 21:32
  • You should pay more attention to a warning like this *"Warning: Unchecked call to 'plus(S)' as a member of raw type 'RealNumber'."*. Like this warning and @SotiriosDelimanolis are telling you, don't use a raw `RealNumber` Paramterize it instead: `RealNumber x = new RealNumber<>();` – Tom Mar 20 '15 at 21:39
  • I don't get `sum5`. Why should an `IntNumber` plus a `RealNumber` give an `IntNumber`? – Paul Boddington Mar 20 '15 at 21:59
  • @pbabcdefb, sum5 could throw an exception if the RealNumber in case is not in fact an integer, or it could round it. But this is just an example hierarchy. It makes more sense in some other use cases, and I should have called these classes simply A, B and C here. But pay attention to sum6, where you add an integer to an integer and it also doesn't work. This is the real problem. – Marcelo Glasberg Mar 20 '15 at 22:11
  • @SotiriosDelimanolis, the problem is that writing `RealNumber` makes no sense, and `RealNumber` should be unnecessary. So maybe the entire use of generics like this is just plain wrong. 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. Or not? – Marcelo Glasberg Mar 20 '15 at 22:21
  • I have asked another question that better reflects my problem, here: http://stackoverflow.com/questions/29188497/use-of-generics-in-a-final-method-that-returns-a-value-of-the-same-type-of-its-o – Marcelo Glasberg Mar 21 '15 at 21:58

2 Answers2

2

I'm assuming what you are trying to create is a type hierarchy that expresses "Addables" which return, given two "operands", the more general of the two, using inheritance to model the mathematical relationship of "special case".

Before I go any further, you should consider this link which uses the Square is-a Rectangle example to illustrate why that use of inheritance is often a bad idea, but in the case of immutable objects like the ones you're proposing you might be ok.

You want both Int.plus( Real ) and Real.plus( Int ) to return Real, and Int.plus( Int ) to return Int. The same pattern should work for Nat.plus( Int ), etc.

As long as all the types are statically known, that should be a piece of cake type-wise.

class Real {
    Real plus( Real r ) { ... }
    Int floor() { ... }
}
class Int extends Real {
    Int plus( Int i ) {
        return plus( i.asReal() ).floor();
    }
    Nat abs() { ... }
    Real asReal() {
        return this;
    }
}
class Nat extends Int {
    Nat plus( Nat n ) {
        return plus( n.asInt() ).abs();
    }
    Int asInt() {
        return this;
    }
}

Overloading plus allows the compiler to determine the "most specific" plus operation that can be statically verified. The compiler can't know that adding two reals that happen to be integers results in another real that's an integer. For that matter, some non-integers add up to integers; I'm sure you don't intend for the type system to represent that fact somehow.

Under this arrangement, all but sum5, sum9, and sum10 type check, which is (to my mind) exactly as it should be.

Incidentally, with respect to the "curiously recurring type constraint" pattern you were trying to demonstrate, you're doing it slightly wrong. You need to supply a type parameter everywhere the generic type occurs, even inside the type bound itself:

class G< T extends G< T > > {}

You, and all the other commenters and responders here, have been leaving off the second T.

Judge Mental
  • 5,209
  • 17
  • 22
  • One thing I learned from this question of mine is that in SO you should always name your classes A, B, C. If you don't, people start to question your model. You say `Int.plus(Real)` should return a `Real`. However, my actual classes here are not Reals and Integers, but some other complicated business classes. They are "addables", yes, but here A.plus(B) should return A, for every A and B. – Marcelo Glasberg Mar 21 '15 at 20:19
  • I have asked another question that better reflects my problem, here: http://stackoverflow.com/questions/29188497/use-of-generics-in-a-final-method-that-returns-a-value-of-the-same-type-of-its-o – Marcelo Glasberg Mar 21 '15 at 21:58
1

All of the comments above are right. if you define a generic class you should define it also if you use it.

public class GenericNumber {

    public static void main(String[] args) {
        // ------
        // ------ The important change
        // ------
        RealNumber<RealNumber> x = new RealNumber<>();            
        IntNumber<IntNumber> y = new IntNumber<>();
        PositiveIntNumber z = new PositiveIntNumber();
        // Everything works fine
        RealNumber sum1 = x.plus(x); 
        RealNumber sum2 = x.plus(y); 
        RealNumber sum3 = y.plus(x); 
        RealNumber sum4 = y.plus(y); 
        IntNumber sum5 = y.plus(x); 
        IntNumber sum6 = y.plus(y); 
        PositiveIntNumber sum9 = z.plus(x);  
        PositiveIntNumber sum10 = z.plus(y); 
        PositiveIntNumber sum11 = z.plus(z); 
    }

    public static class RealNumber<S extends RealNumber> {

        public <T extends RealNumber> S plus(T realNumber) {
            return null;
        }
    }

    public static class IntNumber<S extends IntNumber> extends RealNumber<S> {
    }

    public static class PositiveIntNumber extends IntNumber<PositiveIntNumber> {
    }

}
drkunibar
  • 1,327
  • 1
  • 7
  • 7
  • The problem is that writing `RealNumber` should be unnecessary, since `RealNumber` and `RealNumber` make absolutely no sense. So maybe the entire use of generics like this is just plain wrong. 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. Or not? – Marcelo Glasberg Mar 20 '15 at 22:22
  • You are right. In your case `RealNumber` makes no sense - but it is possible with your definition. If you define a generic type like `RealNumber` you have to declare the generic type if you use it (i.e. RealNumber). Have a look at http://docs.oracle.com/javase/tutorial/java/generics/types.html#instantiation – drkunibar Mar 20 '15 at 22:36
  • So my definition is wrong. This I knew from the start. My question is how to fix this. I need to have a new definition... Use generics in some different way then the one I presented... Or maybe generics is the wrong tool, and I should just repeat the `plus` method over and over again?... – Marcelo Glasberg Mar 20 '15 at 22:42
  • In your `plus`-method you cannot use anything like `return new RealNumber();`. It not easy to say if generics are the right decision, cause I don't know what you really want to do. It'slate now and I go to sleep. I will have a look at this problem tomorrow. – drkunibar Mar 20 '15 at 23:04
  • I have asked another question that better reflects my problem, here: http://stackoverflow.com/questions/29188497/use-of-generics-in-a-final-method-that-returns-a-value-of-the-same-type-of-its-o – Marcelo Glasberg Mar 21 '15 at 21:59