Java: métodos de sobrecarga (genéricos) para java.lang.Number

Así que leí 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.
Este fue mi primer intento:

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?

preguntado el 08 de noviembre de 11 a las 15:11

Java does not allow you to override a method with a different return value. If you want another add método que devuelve un RealNumber then I would call it addReal() o algo. -

For float and double you can use doubleValue() for int and long you can use longValue() Para int también puedes usar 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. -

@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) -

2 Respuestas

El :

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);
    }

respondido 08 nov., 11:19

This doesn't work either for the same reason. The type erasure won't call the right add(Integer ...) or other methods. - Gray

Yes, you are right. I don't think you can actually get out of using instanceof and dinamically instanciating the needed classes... - Dragón Shivan

@AndreiBodnarescu bad to hear. So maybe I have to live with the solution, that one can add an Integer to a RealNumber<Double> which will lead in a RealNumber<Integer> - Rafael T

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. - Dragón Shivan

@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

[ 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.

respondido 08 nov., 11:19

"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

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 compilador 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: download.oracle.com/javase/tutorial/java/generics/erasure.html - Gray

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.