Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to write a generic +1 method for numeric box types in Java?

This is NOT homework.

Part 1

Is it possible to write a generic method, something like this:

<T extends Number> T plusOne(T num) {
    return num + 1; // DOESN'T COMPILE! How to fix???
}

Short of using a bunch of instanceof and casts, is this possible?


Part 2

The following 3 methods compile:

Integer plusOne(Integer num) {
    return num + 1;
}   
Double plusOne(Double num) {
    return num + 1;
}
Long plusOne(Long num) {
    return num + 1;
}

Is it possible to write a generic version that bound T to only Integer, Double, or Long?

like image 831
polygenelubricants Avatar asked May 14 '10 13:05

polygenelubricants


3 Answers

Part 1

There is no satisfactory solution for this, since java.lang.Number doesn't specify anything that would be useful to compute the successor of a Number.

You'd have to do instanceof checks for the numeric box types, and handle each case specially. Note also that you may get an instanceof Number that's none of the numeric box types, e.g. BigInteger, AtomicLong, and potentially unknown subclasses of Number (e.g. Rational, etc).

Part 2

Look is very deceiving, here. The 3 methods may look alike, but autoboxing/unboxing hides the fact that they're actually very different at the bytecode level:

Integer plusOne(Integer);
  Code:
   0:   aload_1
   1:   invokevirtual   #84; //int Integer.intValue()
   4:   iconst_1
   5:   iadd
   6:   invokestatic    #20; //Integer Integer.valueOf(int)
   9:   areturn

Double plusOne(Double);
  Code:
   0:   aload_1
   1:   invokevirtual   #91; //double Double.doubleValue()
   4:   dconst_1
   5:   dadd
   6:   invokestatic    #97; //Double Double.valueOf(double)
   9:   areturn

Long plusOne(Long);
  Code:
   0:   aload_1
   1:   invokevirtual   #102; //Long Long.longValue()
   4:   lconst_1
   5:   ladd
   6:   invokestatic    #108; //Long Long.valueOf(long)
   9:   areturn

Not only are the 3 methods invoking different xxxValue() and valueOf() methods on different types, but the instruction to push the constant 1 to the stack is also different (iconst_1, dconst_1, and lconst_1).

Even if it's possible to bind a generic type like <T=Integer|Long|Double>, the 3 methods are not genericizable into one method since they contain very different instructions.

like image 157
polygenelubricants Avatar answered Nov 15 '22 04:11

polygenelubricants


Not all of the subclasses of Number can be autounboxed. BigDecimal, for instance, can't be autounboxed. Therefore the "+" operator won't work for it.

like image 27
Jim Kiley Avatar answered Nov 15 '22 06:11

Jim Kiley


Not the prettiest solution ever, but if you rely in the following properties of every known implementation of Number (in the JDK):

  • They can all be created from their String representation via a one-argument constructor
  • None of them has numbers that can't be represented by BigDecimal

You can implement it using reflection and using Generics to avoid having to cast the result:

public class Test {

    @SuppressWarnings("unchecked")
    public static <T extends Number> T plusOne(T num) {
        try {
            Class<?> c = num.getClass();
            Constructor<?> constr = c.getConstructor(String.class);
            BigDecimal b = new BigDecimal(num.toString());
            b = b.add(java.math.BigDecimal.ONE);
            return (T) constr.newInstance(b.toString());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        System.out.println(plusOne(1));
        System.out.println(plusOne(2.3));
        System.out.println(plusOne(2.4E+120));
        System.out.println(plusOne(2L));
        System.out.println(plusOne(4.5f));
        System.out.println(plusOne(new BigInteger("129481092470147019409174091790")));
        System.out.println(plusOne(new BigDecimal("12948109247014701940917.4091790")));
    }

}

The return is done using an apparently unsafe cast but given that you're using a constructor of the class of some T or child of T you can assure that it will always be a safe cast.

like image 30
Santi P. Avatar answered Nov 15 '22 06:11

Santi P.