MENU

Java杂记(二)关于 Java 自动装箱的理解

June 11, 2018 • Code

前言:学习 Java 的自动装箱机制,并通过栗子理解自动装箱的本质。

主要过程

什么是自动拆装箱

来自 The Java™ Tutorials

Autoboxing is the automatic conversion that the Java compiler makes between the primitive types and their corresponding object wrapper classes. For example, converting an int to an Integer, a double to a Double, and so on. If the conversion goes the other way, this is called unboxing.

简单来说,自动装箱是 Java 编译器自动将基本数据类型转换为对应包装类,自动拆箱则相反。举个栗子,int 类型数据自动转为 Integer 为装箱,Integer 类型自动转为 int 为拆箱。

自动装箱是 Java 在 1.5 引入的语法糖,目的是简化代码编写。但如果不理解内在转换过程,则很容易踩坑。

关于自动装箱机制的一道题

题目

/**
 * @author kbrx93
 */
public class AutoBox {

    public static void main(String[] args) {
        Integer a = 1;
        int temp = a;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        
        // 1 结果为 true
        System.out.println(c == d);
        
        // 2 结果为 false
        System.out.println(e == f);
        
        // 3 结果为 true
        System.out.println(c == (a + b));
        
        // 4 结果为 true
        System.out.println(c.equals(a + b));
        
        // 5 结果为 true
        System.out.println(g == (a + b));
        
        // 6 结果为 false
        System.out.println(g.equals(a + b));

    }
}

分析

将上面的程序用 Jad 反编译,结果如下:

public class AutoBox
{

    public AutoBox()
    {
    }

    public static void main(String args[])
    {
        Integer a = Integer.valueOf(1);
        int temp = a.intValue();
        Integer b = Integer.valueOf(2);
        Integer c = Integer.valueOf(3);
        Integer d = Integer.valueOf(3);
        Integer e = Integer.valueOf(321);
        Integer f = Integer.valueOf(321);
        Long g = Long.valueOf(3L);
        System.out.println(c == d);
        System.out.println(e == f);
        System.out.println(c.intValue() == a.intValue() + b.intValue());
        System.out.println(c.equals(Integer.valueOf(a.intValue() + b.intValue())));
        System.out.println(g.longValue() == (long)(a.intValue() + b.intValue()));
        System.out.println(g.equals(Integer.valueOf(a.intValue() + b.intValue())));
    }
}

通过反编译的内容可看出:自动装箱实质上是调用 XXX.valueOf(xxx),自动拆箱是调用 XXX实例对象.xxxValue()XXX 指包装类,xxx 指对应基本类型。

语句一
Integer b = Integer.valueOf(2);
Integer c = Integer.valueOf(3);
// 1 结果为 true
System.out.println(c == d);

语句 1 输出 true:用 == 比较的是引用地址,按道理说 valueOf 方法返回两个对象 c 与 d 应为不同,但结果为 true 则表明 c 与 d 为同一个对象。这是因为包装类中存在一个缓存数组。

以 Integer 为栗,valueOf 方法源码如下:

    /**
     * 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);
    }

由上面的源码可知:当 i 处于 IntegerCache.low <= i <= IntegerCache.high 的范围时,会直接返回 IntegerCache 数组中的元素,不会新生成一个对象。

Integer i = new Integer(3) 则会生成新对象。

缓存数组范围在不同包装类中值不同:Byte,Short,Long 固定范围 -128 到 127。Character 固定范围 0 到 127。Integer 默认 -128 到 127,但最高值 127 可配置,其它类范围则不可修改。

语句二
Integer e = Integer.valueOf(321);
Integer f = Integer.valueOf(321);
// 2 结果为 false
System.out.println(e == f);
  • 语句二返回的结果为 false,基本运行过程中与语句一相同,返回 false 是因为 321 超过默认的缓存数组范围,所以返回的 e 与 f 为新生成两个对象。
语句三
// 3 结果为 true
// System.out.println(c.intValue() == a.intValue() + b.intValue());
System.out.println(c == (a + b));
  • 遇到算术运算时会自动拆箱。
语句四
// 4 结果为 true
// System.out.println(c.equals(Integer.valueOf(a.intValue() + b.intValue())));
System.out.println(c.equals(a + b));
  • 遇到 equals 的时候会自动装箱。
语句五 & 六
// 5 结果为 true
// System.out.println(g.longValue() == (long)(a.intValue() + b.intValue())); 
System.out.println(g == (a + b));
    
// 6 结果为 false
// System.out.println(g.equals(Integer.valueOf(a.intValue() + b.intValue())));
System.out.println(g.equals(a + b));
  • 自动拆箱遇到需要强转的场景会自动插入强转代码。

  • equals 不会处理数据转型问题。

小结

自动拆装箱机制为开发者提供了便利,但通过上面题目也说明了如果使用不当,可能会踩坑。所以学习其原理是十分必要的。可以少踩坑。

Last Modified: July 5, 2018
Archives QR Code
QR Code for this page
Tipping QR Code