Java如何决定数学表达式中的哪个运算符必须(un)装箱?

时间:2015-06-01 12:51:02

标签: java autoboxing optimization

我目前正在撰写有关如何编写有效Java代码的学士论文。以下四个代码段是JMH基准测试的一部分,每个方法将执行每个方法100万次。

public final static int primitiveOnly(int dummy, int add1, int add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}
public final static int primitiveToWrapper(int dummy, int add1, Integer add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}
public final static int wrapperToPrimitive(Integer dummy, Integer add1, int add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}
public final static Integer wrapperToWrapper(Integer dummy, Integer add1, Integer add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

这四种方法的结果是:

  • primitiveOnly:1.7毫秒/操作
  • primitiveToWrapper:2.2 ms / operation
  • wrapperToPrimitive:47.5 ms / operation
  • wrapperToWrapper:48.2 ms / operation

这种行为的原因是在primitiveToWrapper的操作期间,Integer值只需要取消装箱,其中wrapperToPrimitive中的操作必须将第一个操作数装入Integer,这会导致昂贵的对象创建。

Java有这样的特定原因吗?我通读了Java Language Specification,但无法找到这个问题的答案。

更新

为了解决关于返回值的问题(感谢Phil Anderson),我更新了我的代码。另外,我将基准类中的所有Integer变量更改为int。这是新版本:

public final static int primitiveOnly(int dummy, int add1, int add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

public final static int primitiveToWrapperIntDummy(int dummy, int add1, Integer add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

public final static Integer primitiveToWrapperIntegerDummy(Integer dummy, int add1, Integer add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

public final static int wrapperToPrimitiveIntDummy(int dummy, Integer add1, int add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

public final static Integer wrapperToPrimitiveIntegerDummy(Integer dummy, Integer add1, int add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

public final static int wrapperToWrapperIntDummy(int dummy, Integer add1, Integer add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

public final static Integer wrapperToWrapperIntegerDummy(Integer dummy, Integer add1, Integer add2) {
    for(int i = 0; i < 10; i++) {
        dummy += (add1 + add2);
    }
    return dummy;
}

结果平均为10次迭代(1次迭代=上述每种方法的100次执行)。

  • originalOnly:0.783
  • primitiveToWrapper(int dummy):0.735
  • primitiveToWrapper(Integer dummy):24.999
  • WrapperToPrimitive(int dummy):0.709
  • WrapperToPrimitive(Integer dummy):26.782
  • WrapperToWrapper(int dummy):0.764
  • WrapperToWrapper(Integer dummy):27.301

最终结果现在感觉更加直观。谢谢大家帮助我。

2 个答案:

答案 0 :(得分:3)

在代码的第二位,每次为虚拟java分配一个值都必须将它装入一个Integer,因为这是变量的类型。它不知道你从来没有真正调用任何方法,它可能是一个简单的int。

因此每次遇到代码dummy += (add1 + add2);时,都必须执行以下操作。

  • Unbox dummy
  • 执行添加
  • 将结果反馈回虚拟

每次通过for循环都会这样做。

答案 1 :(得分:2)

这是因为当dummy是一个整数时,它的值是不可变的。参见例如Why are Integers immutable in Java?

基本上,在写dummy += (add1 + add2);的最后一种方法中,它意味着

dummy = Integer.valueOf(dummy.intValue() + add1.intValue() + add2.intValue());

每次循环时,都需要分配一个新对象来保留一个新的整数值。