2

So I read discussion about Numbers here, because I have a similar Problem. In my case, I wanna be able to allow mathematical operations on Numbers. My idea was to write an immutable ´RealNumber´ class that handles primitive Numbers (Integer, Long, Float and Double), without a bunch of instanceof controls. Someone mentioned to Overload Methods and let the compiler do the work.
This was my first attempt:

simple TestClass:

public class Test {
public static void main(String[] args) {
    RealNumber<Double> d = RealNumber.create(3.4);
    d.add(4.7);
    }
}

RealNumber class: (please mention the Method with the comment)

public class RealNumber<N extends Number> extends Number{

    N number;   

    private RealNumber(N number){
        if (number == null){
            throw new NullPointerException("number is null");
        }
        this.number = number;
    }

    public N get(){
        return number;
    }

    //note this Method
    public RealNumber<N> add(N number){
        return add(number);
    }

    private RealNumber<Integer> add(Integer number){
        return new RealNumber<Integer>(intValue() + number);
    }

    private RealNumber<Long> add(Long number){
        return new RealNumber<Long>(longValue() + number);
    }

    private RealNumber<Float> add(Float number){
        return new RealNumber<Float>(floatValue() + number);
    }

    private RealNumber<Double> add(Double number){
        return new RealNumber<Double>(doubleValue() + number);
    }

    @Override
    public int intValue() {
        return number.intValue();
    }

    @Override
    public long longValue() {
        return number.longValue();
    }

    @Override
    public float floatValue() {
        return number.floatValue();
    }

    @Override
    public double doubleValue() {
        return number.doubleValue();
    }


    public static final RealNumber<Integer> create(Integer number){
        return new RealNumber<Integer>(number);
    }

    public static final RealNumber<Long> create(Long number){
        return new RealNumber<Long>(number);
    }

    public static final RealNumber<Float> create(Float number){
        return new RealNumber<Float>(number);
    }

    public static final RealNumber<Double> create(Double number){
        return new RealNumber<Double>(number);
    }
}

so the first test leads me to a StackOverflowError, because the method ´add´ always calls itself.

second try (only changed methods)

public RealNumber<N> add(Number number){
    return add(number);
}

first wasn't as good, cause it will allow to add BigDecimals, or other things like Boolean, and second leads me to the same StackOverflowError. so I changed:

public RealNumber<N> add(N number){
    return add(number);
}

//note the public here
public RealNumber<Double> add(Double number){
    return new RealNumber<Double>(doubleValue() + number);
}
//... public RealNumber<Integer, Long, Float> add....

which fails to compile in my TestClass -> "The Method add(Double) is ambiguous for the Type RealNumber
finally this worked:

public RealNumber<N> add(Number number){
    return add(number);
}

//note the public here
public RealNumber<Double> add(Double number){
    return new RealNumber<Double>(doubleValue() + number);
}
//... public RealNumber<Integer, Long, Float> add....

but brings another 2 issues: this pattern allows to add Doubles to Ints (which results in RealNumber of Integer ), and results in a StackOverflowError if one passes a BigInteger, Byte or some other Number.

So my main questions:

Why the compiler chooses the right method in Test.class if every add Method is public and fails, if they are private.

What can I do to fix the issues?

Community
  • 1
  • 1
Rafael T
  • 15,401
  • 15
  • 83
  • 144
  • Java does not allow you to override a method with a different return value. If you want another `add` method that returns a `RealNumber` then I would call it `addReal()` or something. – Gray Nov 08 '11 at 15:17
  • For float and double you can use `doubleValue()` for int and long you can use `longValue()` For `int` you can also use `doubleValue()` Unless you need to represent all `long` values accurately, you can just use `double` or `Double`, if they need to be accurate you can use `BigDecimal`. – Peter Lawrey Nov 08 '11 at 15:22
  • @Gray I couldn't name it addReal, because then I don't have overloading. Second the difference there is add(Number number) and add(N number) – Rafael T Nov 08 '11 at 15:22

2 Answers2

2

[ Sorry, I didn't fully understand the question with my first try. ]

I don't think there is an easy answer here @Rafael because as @Andrei Bodnarescu pointed out, type erasure means that you do not have the type of your N parameter at runtime. I think you have to provide a concrete implementation of your add() method for each subclass of Number.

public RealNumber<Integer> add(Integer number) {
    return new RealNumber<Integer>(intValue() + number);
}
public RealNumber<Long> add(Long number) {
    return new RealNumber<Long>(longValue() + number);
}

If you don't want to add integers to doubles then I guess you will need to do something like:

public RealNumber<Integer> add(Integer number){
    if (!(this instanceof Integer)) {
        throw new IllegalArgumentException("You can't do this...");
    }
    return new RealNumber<Integer>(intValue() + number);
}

I don't see any easy way to work around this.

Gray
  • 115,027
  • 24
  • 293
  • 354
  • "type erasure means that you do not have the type of your N parameter at runtime."... are you shure, I thought I didn't have the type of N if i DON'T have an instance of it. in my case I have, and I can just call inside my add(N number) the method number.getClass().getSimpleName() which returns me the correct Type i put inside – Rafael T Nov 08 '11 at 15:58
  • 1
    You can certainly get the class of the parameter and call the right `add()` method based on an `if/else` block based on the class. But there is no way for the _compiler_ to do that routing for you. The compiler has no idea if the parameter is an Integer or a Long. Here's more on type erasure: http://download.oracle.com/javase/tutorial/java/generics/erasure.html – Gray Nov 08 '11 at 16:02
2

The :

public RealNumber<N> add(N number){
        return add(number);
    }

method always calls itself because of type erasure: in Java generics are only for compile time, they're no longer present at runtime, so the VM effectively doesn't know what type N is, so it calls the most generic methods available, namely this one. You must pass the type as Class as argument to the method aswell, something like:

public RealNumber<N> add(N number,Class<N> numberClass){
        return add(number);
    }
Shivan Dragon
  • 15,004
  • 9
  • 62
  • 103
  • 1
    This doesn't work either for the same reason. The type erasure won't call the right `add(Integer ...)` or other methods. – Gray Nov 08 '11 at 15:34
  • Yes, you are right. I don't think you can actually get out of using instanceof and dinamically instanciating the needed classes... – Shivan Dragon Nov 08 '11 at 15:39
  • @AndreiBodnarescu bad to hear. So maybe I have to live with the solution, that one can add an Integer to a RealNumber which will lead in a RealNumber – Rafael T Nov 08 '11 at 15:43
  • I'm afraid you've lost me? you can't pass an Integer instance or an int instance to a method that accepts double or Double? and if you overload the same method to accept either integers or doubles, calling it with the approporiate primitives or objects will go to the correct overloaded version of that method. Autoboxing/Unboxing takes care of that. – Shivan Dragon Nov 08 '11 at 15:52
  • @AndreiBodnarescu also it doesn't make sense to me, that Compiler knows the Type if add(Double) is public and doesn't know it if it is private – Rafael T Nov 08 '11 at 15:53