5

这不是家庭作业。

第1部分

是否可以编写一个通用方法,如下所示:

<T extends Number> 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仅绑定到IntegerDouble或的通用版本Long

4

7 回答 7

6

第1部分

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

您必须instanceof检查数字框类型,并专门处理每种情况。另请注意,您可能会得到一个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_1dconst_1lconst_1)。

即使可以绑定一个像 的泛型类型<T=Integer|Long|Double>,这 3 个方法也不能泛化为一个方法,因为它们包含非常不同的指令。

于 2010-05-14T14:14:31.943 回答
5

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

于 2010-05-14T13:40:04.457 回答
4

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

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

您可以使用反射和使用泛型来实现它,以避免强制转换结果:

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

}

返回是使用明显不安全的强制转换完成的,但鉴于您使用的是某个 T 或 T 的子类的构造函数,您可以确保它始终是安全强制转换。

于 2010-05-14T18:54:36.787 回答
2

FWIW 这并不是泛型的真正限制。

+ 运算符仅适用于原语。它适用于 Integer 或 Long 的原因是因为它们的原始类型自动装箱/拆箱。并非所有 Number 子类都有匹配的原始类型,但更重要的是 Number 没有匹配的原始类型。所以完全去掉泛型,下面的代码仍然是错误的:

public Number plusOne(Number num) {
    return num + 1;
}
于 2010-05-14T14:11:28.703 回答
1

第1部分:

不需要num + 1创建这样的方法就行不通吗?运算符为此而+重载。也就是说,为什么要调用:

Integer n = plusOne(anotherInt);

什么时候可以:

Integer n = anotherInt + 1;

底线是 - 你不能将自动装箱与泛型结合起来。

于 2010-05-14T13:43:30.190 回答
1

这里的问题是您的代码必须拆箱对象,对原语进行操作,然后重新装箱。Java 真的不能这样做,因为在编译代码时,它不再知道类型是什么,所以它不知道如何拆箱。

Java 泛型的价值在于真正保护类型安全,即编译器知道真正的类并防止非法赋值。编译器不会根据类型生成不同的代码:它不会说“哦,那是一个整数,所以我需要在这里生成一个整数加法,而不是一个字符串,所以加号实际上意味着字符串连接”。它与 C++ 模板确实有很大不同,如果这是您的想法的话。

完成这项工作的唯一方法是为 Number 定义一个 plusOne 函数,但没有。

于 2010-05-14T14:08:38.023 回答
0

Java 中的算术运算仅适用于原语。你在这里结合泛型和自动装箱拆箱等。

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

于 2010-05-14T13:44:21.747 回答