Is it possible to write a generic +1 method for nu

2020-04-07 10:57发布

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?

7条回答
叼着烟拽天下
2楼-- · 2020-04-07 11:13

Arithmetic operations in Java work only on primitives. You here are combining generics and autoboxing unboxing etc.

For such a simple case as yours I'll suggest use only primitives.

查看更多
萌系小妹纸
3楼-- · 2020-04-07 11:18

The problem here is that your code must unbox the object, operate on the primitive, and then rebox it. Java really can't do that because by the time the code is compiled, it doesn't know what the type is any more, so it doesn't know how to unbox.

The value of Java generics is really to preserve type safety, i.e. the compiler knows the real class and will prevent illegal assignments. The compiler will NOT generate different code depending on the type: it WON'T say "oh, that's an integer, so I need to generate an integer add here, versus that one's a String, so the plus sign really means string concatenation". It's really quite different from C++ templates, if that's what you're thinking of.

The only way you could make this work would be if there was a plusOne function defined for Number, which there isn't.

查看更多
你好瞎i
4楼-- · 2020-04-07 11:20

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.

查看更多
劫难
5楼-- · 2020-04-07 11:21

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.

查看更多
啃猪蹄的小仙女
6楼-- · 2020-04-07 11:25

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.

查看更多
兄弟一词,经得起流年.
7楼-- · 2020-04-07 11:26

Part 1:

Doesn't num + 1 work without the need to create such method? The + operator is overloaded just for that. That is, why call:

Integer n = plusOne(anotherInt);

when you can do:

Integer n = anotherInt + 1;

The bottomline is - you can't combine autoboxing with generics.

查看更多
登录 后发表回答