是否可以在Java中为数字框类型编写通用的+1方法?

这不是功课。

第1部分

是否可以编写generics方法,如下所示:

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

如果没有使用一堆instanceof和强制转换,这可能吗?


第2部分

以下3种方法编译:

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

是否可以编写一个将T绑定到只有IntegerDoubleLong的generics版本?

第1部分

没有令人满意的解决方案,因为java.lang.Number没有指定任何对计算Number的后继有用的东西。

您必须对数字框类型执行instanceof检查,并专门处理每个案例。 另请注意,您可能会获得一个不是数字框类型的AtomicLong instanceof Number ,例如BigIntegerAtomicLong和可能未知的Number子类(例如Rational等)。

第2部分

在这里看起来很欺骗。 这3种方法可能看起来很相似,但自动装箱/拆箱隐藏了它们在字节码级别实际上非常不同的事实:

 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 

3个方法不仅在不同类型上调用不同的xxxValue()valueOf()方法,而且将常量1推送到堆栈的指令也不同( iconst_1dconst_1lconst_1 )。

即使可以绑定像这样的generics类型,这三种方法也不能泛化为一种方法,因为它们包含非常不同的指令。

并非所有Number的子类都可以自动装箱。 例如,BigDecimal无法自动装箱。 因此“+”运算符不适用于它。

不是最漂亮的解决方案,但是如果你依赖于每个已知的Number实现的以下属性(在JDK中):

  • 它们都可以通过单参数构造函数从String表示forms创建
  • 它们都没有无法用BigDecimal表示的数字

您可以使用reflection并使用generics来实现它,以避免必须转换结果:

 public class Test { @SuppressWarnings("unchecked") public static  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"))); } } 

返回是使用明显不安全的强制转换来完成的,但是如果你使用的是某个T类或T的子类的构造函数,你可以确保它总是一个安全的强制转换。

FWIW这并不是对generics的限制。

+运算符仅适用于基元。 它适用于Integer或Long的原因是因为它们的原始类型的自动装箱/拆箱。 并非所有Number子类都具有匹配的基元类型,但更重要的是Number没有匹配的基元类型。 因此,完全从generics中取出generics,以下代码仍然是错误的:

 public Number plusOne(Number num) { return num + 1; } 

第1部分:

num + 1不需要创建这样的方法吗? +运算符仅为此重载。 那就是为什么打电话:

 Integer n = plusOne(anotherInt); 

当你能做到:

 Integer n = anotherInt + 1; 

底线是 – 你不能将autoboxing与generics结合起来。

这里的问题是你的代码必须取消对象的对象,对基元进行操作,然后重新加载它。 Java真的不能这样做,因为在编译代码时,它不知道类型是什么,所以它不知道如何取消框。

Javagenerics的价值实际上是为了保持类型安全,即编译器知道真正的类并且将防止非法赋值。 编译器不会根据类型生成不同的代码:它不会说“哦,这是一个整数,所以我需要在这里生成一个整数,而不是那个是字符串,所以加号实际上意味着字符串连接”。 它与C ++模板完全不同,如果你正在考虑的话。

你可以做这项工作的唯一方法是,如果有一个为Number定义的plusOne函数,则没有。

Java中的算术运算仅适用于基元。 你在这里结合了generics和自动装箱拆箱等。

对于像你这样简单的情况,我建议只使用原语。