是否可以在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
绑定到只有Integer
, Double
或Long
的generics版本?
第1部分
没有令人满意的解决方案,因为java.lang.Number
没有指定任何对计算Number
的后继有用的东西。
您必须对数字框类型执行instanceof
检查,并专门处理每个案例。 另请注意,您可能会获得一个不是数字框类型的AtomicLong
instanceof Number
,例如BigInteger
, AtomicLong
和可能未知的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_1
, dconst_1
和lconst_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和自动装箱拆箱等。
对于像你这样简单的情况,我建议只使用原语。