Best practice on when to use the wrapper class and primitive type in Java

Best practice on when to use the wrapper class and primitive type in Java

四个概念:

  • primitive type:原始类型
  • wrapper class:包装类型
  • autoboxing:自动包装
  • unboxing:解包

对应关系:

Primitive typeWrapper class
booleanBoolean
byteByte
charCharacter
floatFloat
intInteger
longLong
shortShort
doubleDouble

在 Effective Java 的第五项中, Joshua Bloch 有这样的观点:

The lesson is clear: prefer primitives to boxed primitives, and watch out for unintentional autoboxing.

意思就是:相对于 boxed primitive 更喜欢 primitive,并且需要注意无意识的 autoboxing 机制。

类的一个很好的用途是作为泛型类型(包括Collection类,比如list和map),或者当你想要将它们转化为其他类型而不进行隐式转换时(例如 Intege类具有方法 doubleValue() or byteValue())。

因此,最佳实践是能使用primitive的都用primitive,除非你正在处理泛型(确保你知道 autoboxing 和 unboxing)

使用 primitive

在以下几种情况下使用 primitive

primitive 性能更好

//隐式的降低程序速度
public static void main(String[] args) {
    Long sum = 0L;
    for (long i = 0; i < Integer.MAX_VALUE; i++) {
         sum += i;
    }
    System.out.println(sum);
}

这个程序虽然能得到正确 sum ,但是效率比理论中会慢很多。变量 sum 被定义成了Long而不是 long ,这就意味着程序构建了 2^31 次没必要的 Long 实例(每次 long i 被加到 Long sum 上时算一次)。将sum 的类型从 Long 改为 long,程序时间可以达到数量级的缩减。

primitive 可读性更高

Integer a = 2;
Integer b = 2;
if (!a.equals(b)) {
    // ...
}
int c = 2;
int d = 2;
if (c != d) {
    // ...
}

代码中的两种相等比较,多数人都会认为第二种更易读,对于先上手 C++ / Python 的人更是这样。

使用 primitive 可以避免一些错误

如果不了解 wrapper class 中的一些机制,会遇到一些莫名其妙的问题

莫名其妙的 NullPointException

Integer getValue();
...
int myValue = getValue(); // getValue返回null时就会抛出NPE

这个代码可以编译通过,但是会抛出空指针异常。int b = a实际上是int b = a.intValue(),由于a的引用值为null,在空对象上调用方法就会抛出NPE。

wrapper class 的引用相等性

在Java中,== 符号判断的内存地址所对应的值的相等性,具体来说,基本类型判断值是否相等,引用类型判断其指向的地址是否相等。

Integer a1 = 1;
Integer a2 = 1;
System.out.println(a1 == a2); // true

Integer b1 = 222;
Integer b2 = 222;
System.out.println(b1 == b2); // false

这两段代码的结果是不同的,具体需要看下 java.lang.Integer 的 valueOf 方法的源码:

package java.lang;

import java.lang.annotation.Native;

public final class Integer extends Number implements Comparable<Integer> {

    /**
     * Returns an {@code Integer} instance representing the specified
     * {@code int} value.  If a new {@code Integer} instance is not
     * required, this method should generally be used in preference to
     * the constructor {@link #Integer(int)}, as this method is likely
     * to yield significantly better space and time performance by
     * caching frequently requested values.
     *
     * This method will always cache values in the range -128 to 127,
     * inclusive, and may cache other values outside of this range.
     *
     * @param  i an {@code int} value.
     * @return an {@code Integer} instance representing {@code i}.
     * @since  1.5
     */
    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }

    /**
     * The value of the {@code Integer}.
     *
     * @serial
     */
    private final int value;
}

valueOf 方法会缓存 -128 到 127 之间的值,因此第一段代码会取到的是同一个对象,第二段代码会创建两个对象且地址不一样,因此导致的结果不同。

使用 wrapper class

使用泛型的时候必须使用 wrapper class,因为Java不支持使用基本类型作为类型参数

List<int> list; // 编译器会提示:Type argument cannot be of primitive type
List<Integer> list; // 这个就是正确的

参考:


Best practice on when to use the wrapper class and primitive type in Java
https://suncle.me/posts/1389847982/
作者
Suncle Chen
发布于
2019年1月22日
许可协议