Java是面向对象的语言,但并不是“纯面向对象”的,因为我们经常用到的基本数据类型就不是对象。但是我们在实际应用中经常需要将基本数据转化成对象,以便于操作。比如:将基本数据类型存储到 Object[] 数组或集合中的操作等等。

为了解决这个不足,Java 在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。

包装类均位于 java.lang 包,八种包装类和基本数据类型的对应关系如表所示:

img

在这八个类名中,除了 Integer 和 Character 类以外,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写而已。

在这八个类中,除了 Character 和 Boolean 以外,其他的都是“数字型”,“数字型”都是 java.lang.Number 的子类。Number 类是抽象类,因此它的抽象方法,所有子类都需要提供实现。Number 类提供了抽象方法:intValue()、longValue()、floatValue()、doubleValue(),意味着所有的“数字型”包装类都可以互相转型。

8.1 包装类的用途

对于包装类来说,这些类的用途主要包含两种:

  1. 作为和基本数据类型对应的类型存在,方便涉及到对象的操作,如 Object[]、集合等的操作。
  2. 包含每种基本数据类型的相关属性如最大值、最小值等,以及相关的操作方法(这些操作方法的作用是在基本数据类型、包装类对象、字符串之间提供相互之间的转化!)。
public class Test {
    /** 测试Integer的用法,其他包装类与Integer类似 */
    void testInteger() {
        // 基本类型转化成Integer对象
        Integer int1 = new Integer(10);
        Integer int2 = Integer.valueOf(20); // 官方推荐这种写法
        // Integer对象转化成int
        int a = int1.intValue();
        // 字符串转化成Integer对象
        Integer int3 = Integer.parseInt("334");
        Integer int4 = new Integer("999");
        // Integer对象转化成字符串
        String str1 = int3.toString();
        // 一些常见int类型相关的常量
        System.out.println("int能表示的最大整数:" + Integer.MAX_VALUE); 
    }
    public static void main(String[] args) {
        Test test  = new Test();
        test.testInteger();
    }
}

8.2 自动装箱和拆箱

自动装箱和拆箱就是将基本数据类型和包装类之间进行自动的互相转换。JDK1.5后,Java引入了自动装箱(autoboxing)/拆箱(unboxing)。

自动装箱:

基本类型的数据处于需要对象的环境中时,会自动转为“对象”。

我们以 Integer 为例:在 JDK1.5 以前,这样的代码 Integer i = 5 是错误的,必须要通过Integer i = new Integer(5) 这样的语句来实现基本数据类型转换成包装类的过程;而在 JDK1.5 以后,Java 提供了自动装箱的功能,因此只需Integer i = 5这样的语句就能实现基本数据类型转换成包装类,这是因为 JVM 为我们执行了Integer i = Integer.valueOf(5)这样的操作,这就是 Java 的自动装箱。

自动拆箱:

每当需要一个值时,对象会自动转成基本数据类型,没必要再去显式调用 intValue()、doubleValue() 等转型方法。如 Integer i = 5;int j = i; 这样的过程就是自动拆箱。

总结:

自动装箱过程是通过调用包装类的 valueOf() 方法实现的,而自动拆箱过程是通过调用包装类的 xxxValue() 方法实现的(xxx 代表对应的基本数据类型,如 intValue()、doubleValue() 等)。

自动装箱与拆箱的功能事实上是编译器来帮的忙,编译器在编译时依据您所编写的语法,决定是否进行装箱或拆箱动作。

Integer i = 100; // 自动装箱
// 相当于编译器自动为您作以下的语法编译:
Integer i = Integer.valueOf(100); // 调用的是valueOf(100),而不是new Integer(100)
Integer i = 100;
int j = i; // 自动拆箱
// 相当于编译器自动为您作以下的语法编译:
int j = i.intValue();

所以自动装箱与拆箱的功能是所谓的“编译器蜜糖(Compiler Sugar)”,虽然使用这个功能很方便,但在程序运行阶段需要了解 Java 的语义。例如如下所示的程序是可以通过编译的:

public class Test1 {
    public static void main(String[] args) {
        Integer i = null;
        int j = i; // 相当于int j = i.intValue();会抛出空指针异常,
    }
}

null 表示 i 没有指向任何对象的实体,但作为对象名称是合法的(不管这个对象名称存是否指向了某个对象的实体)。由于实际上i并没有指向任何对象的实体,所以也就不可能操作 intValue() 方法,这样上面的写法在运行时就会出现 NullPointerException 错误。

8.3 包装类的缓存问题

整型、char 类型所对应的包装类,在自动装箱时,对于 -128~127 之间的值会进行缓存处理,其目的是提高效率。

缓存处理的原理为:如果数据在 -128~127 这个区间,那么在类加载时就已经为该区间的每个数值创建了对象,并将这 256 个对象存放到一个名为 cache 的数组中。每当自动装箱过程发生时(或者手动调用 valueOf() 时),就会先判断数据是否在该区间,如果在则直接获取数组中对应的包装类对象的引用,如果不在该区间,则会通过 new 调用包装类的构造方法来创建对象。

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

这段代码中我们需要解释下面几个问题:

  1. IntegerCache 类为 Integer 类的一个静态内部类,仅供 Integer 类使用。
  2. 一般情况下 IntegerCache.low 为 -128,IntegerCache.high 为 127,IntegerCache.cache 为内部类的一个静态属性。
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

由上面的源码我们可以看到,静态代码块的目的就是初始化数组 cache 的,这个过程会在类加载时完成。

public class TestWrappedClass {
    public static void main(String[] args) {
        Integer i1 = 123;
        Integer i2 = 123;
        System.out.println(i1 == i2); // true
        System.out.println(i1.equals(i2)); // true

        Integer i3 = 1234;
        Integer i4 = 1234;
        System.out.println(i3 == i4); // false
        System.out.println(i3.equals(i4)); // false
    }
}

注意:

  1. JDK1.5 以后,增加了自动装箱与拆箱功能,如:Integer i = 100; int j = new Integer(100);
  2. 自动装箱调用的是 valueOf() 方法,而不是 new Integer() 方法。
  3. 自动拆箱调用的 xxxValue() 方法。
  4. 包装类在自动装箱时为了提高效率,对于 -128~127 之间的值会进行缓存处理。超过范围后,对象之间不能再使用==进行数值的比较,而是使用 equals 方法。