专注Java教育14年 全国咨询/投诉热线:400-8080-105
动力节点LOGO图
始于2009,口口相传的Java黄埔军校
首页 hot资讯 Java自动装箱和拆箱

Java自动装箱和拆箱

更新时间:2022-08-30 09:38:37 来源:动力节点 浏览398次

Java自动装箱和拆箱是什么?动力节点小编来为大家进行详细介绍。

简述

自动装箱和自动拆箱是两个相反的过程,自动装箱即将基本数据类型转换为对应的封装类,自动拆箱即将封装类转换为对应的基本数据类型。此外,装箱的过程会增加内存的消耗,影响性能,因为这个过程会创建对应的对象。

可进行自动装箱和自动拆箱的类型如下图所示:

自动装箱和自动拆箱

采用如下示例说明自动装箱和自动拆箱的原理。

public class Main {
    public static void main(String[] args) {
        Integer integerNum = 100; // 进行自动装箱,得到的是封装类
        int intNum = integerNum; // 进行自动拆箱,得到基本数据类型
    }
}

通过 javap -c Main.class 查看生成的字节码文件。

Compiled from "Main.java"
public class club.wadreamer.test.Main {
  public club.wadreamer.test.Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
  public static void main(java.lang.String[]);
    Code:
       0: bipush        100
       2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       5: astore_1
       6: aload_1
       7: invokevirtual #3                  // Method java/lang/Integer.intValue:()I
      10: istore_2
      11: return
}

Integer#valueOf() 和 Integer#intValue() 的源码如下:

// 自动装箱
public static Integer valueOf(int i) {
	if (i >= IntegerCache.low && i <= IntegerCache.high)
		return IntegerCache.cache[i + (-IntegerCache.low)];
	return new Integer(i);
}
// 自动拆箱
public int intValue() {
	return value;
}

从上述字节码可以得出如下结论:

在进行自动装箱时,Java 虚拟机会自动调用 Integer#valueOf()。

在进行自动拆箱时,Java 虚拟机会自动调用 Integer#intValue()。

其他数据类型的自动装箱和自动拆箱的过程和 Integer 类似,都是调用类似 xxxValue()、valueOf() 等方法。

经典案例分析

1.空指针异常

示例代码如下所示:

public class Main {
    public static void main(String[] args) {
        Integer integerNum = null;
        int intNum = integerNum; // java.lang.NullPointerException
    }
}

上述两行代码能够通过编译,但在运行时会抛出空指针异常。因为在对 int intNum = integerNum; 进行自动拆箱时,等价于对 null 执行 intValue() 方法。所以,有拆箱操作时一定要特别注意封装类对象是否为null。

2.equals() 和 ==

示例代码如下所示:

public class Main {
    public static void main(String[] args) {
        Integer i1 = 300;
        int i2 = 300;
        Long sum = 600L;
        System.out.println("i1 == i2 -> " + (i1 == i2));
        System.out.println("i1.equals(i2) -> " + i1.equals(i2));
        System.out.println("sum == (i1 + i2) -> " + (sum == (i1 + i2)));
        System.out.println("sum.equals(i1 + i2) -> " + sum.equals(i1 + i2));
    }
}
// 结果如下:
i1 == i2 			-> true
i1.equals(i2) 		-> true
sum == (i1 + i2)	-> true
sum.equals(i1 + i2) -> false

通过 javap -c Main.class 查看生成的字节码文件。

Compiled from "Main.java"
public class club.wadreamer.test.Main {
  public club.wadreamer.test.Main();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
  public static void main(java.lang.String[]);
    Code:
       0: sipush        300
       3: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       6: astore_1
       7: sipush        300
      10: istore_2
      11: ldc2_w        #3                  // long 600l
      14: invokestatic  #5                  // Method java/lang/Long.valueOf:(J)Ljava/lang/Long;
      17: astore_3
      18: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      21: new           #7                  // class java/lang/StringBuilder
      24: dup
      25: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
      28: ldc           #9                  // String i1 == i2 ->
      30: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      33: aload_1
      34: invokevirtual #11                 // Method java/lang/Integer.intValue:()I
      37: iload_2
      38: if_icmpne     45
      41: iconst_1
      42: goto          46
      45: iconst_0
      46: invokevirtual #12                 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
      49: invokevirtual #13                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      52: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      55: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      58: new           #7                  // class java/lang/StringBuilder
      61: dup
      62: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
      65: ldc           #15                 // String i1.equals(i2) ->
      67: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      70: aload_1
      71: iload_2
      72: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      75: invokevirtual #16                 // Method java/lang/Integer.equals:(Ljava/lang/Object;)Z
      78: invokevirtual #12                 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
      81: invokevirtual #13                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      84: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      87: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      90: new           #7                  // class java/lang/StringBuilder
      93: dup
      94: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
      97: ldc           #17                 // String sum == (i1 + i2) ->
      99: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     102: aload_3
     103: invokevirtual #18                 // Method java/lang/Long.longValue:()J
     106: aload_1
     107: invokevirtual #11                 // Method java/lang/Integer.intValue:()I
     110: iload_2
     111: iadd
     112: i2l
     113: lcmp
     114: ifne          121
     117: iconst_1
     118: goto          122
     121: iconst_0
     122: invokevirtual #12                 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
     125: invokevirtual #13                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
     128: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     131: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
     134: new           #7                  // class java/lang/StringBuilder
     137: dup
     138: invokespecial #8                  // Method java/lang/StringBuilder."<init>":()V
     141: ldc           #19                 // String sum.equals(i1 + i2) ->
     143: invokevirtual #10                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
     146: aload_3
     147: aload_1
     148: invokevirtual #11                 // Method java/lang/Integer.intValue:()I
     151: iload_2
     152: iadd
     153: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     156: invokevirtual #20                 // Method java/lang/Long.equals:(Ljava/lang/Object;)Z
     159: invokevirtual #12                 // Method java/lang/StringBuilder.append:(Z)Ljava/lang/StringBuilder;
     162: invokevirtual #13                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
     165: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     168: return
}

我们知道 == 比较的是两个对象的地址是否相等或判断两个基本数据类型的值是否相等,而从字节码和运行结果可以看出,在做 == 运算时,字节码 34 行调用了 intValue(),即进行了自动拆箱。此外,从字节码 107 行可以得出,在进行 “+” 运算时,会进行自动拆箱。

equals() 比较的是内容本身,Integer 和 Long 的 equals() 源码如下所示。可以看到,传入的参数是一个 Object 对象,而我们在程序中传入的是基本数据类型,所以会进行自动装箱。此外,在比较内容本身之前,会先判断两者的封装类的类型是否一致,若不一致,则直接返回 false。

// Integer 的 equals() 源码
public boolean equals(Object obj) {
	if (obj instanceof Integer) {
		return value == ((Integer)obj).intValue();
	}
	return false;
}
// Long 的 equals() 源码
public boolean equals(Object obj) {
	if (obj instanceof Long) {
		return value == ((Long)obj).longValue();
	}
	return false;
}

3.自动装箱的缓存机制

示例代码如下所示:

public class Main {
    public static void main(String[] args) {
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 300;
        Integer i4 = 300;
        Double d1 = 100d;
        Double d2 = 100d;
        Double d3 = 300d;
        Double d4 = 300d;
        Float f1 = 100f;
        Float f2 = 100f;
        Float f3 = 300f;
        Float f4 = 300f;
        Boolean b1 = false;
        Boolean b2 = false;
        Boolean b3 = true;
        Boolean b4 = true;
        System.out.println("i1 == i2 -> " + (i1 == i2));
        System.out.println("i3 == i4 -> " + (i3 == i4));
        System.out.println("d1 == d2 -> " + (d1 == d2));
        System.out.println("d3 == d4 -> " + (d3 == d4));
        System.out.println("f1 == f2 -> " + (f1 == f2));
        System.out.println("f3 == f4 -> " + (f3 == f4));
        System.out.println("b1 == b2 -> " + (b1 == b2));
        System.out.println("b3 == b4 -> " + (b3 == b4));
    }
}
// 结果如下:
i1 == i2 -> true
i3 == i4 -> false
d1 == d2 -> false
d3 == d4 -> false
f1 == f2 -> false
f3 == f4 -> false
b1 == b2 -> true
b3 == b4 -> true

接下来看下 Integer#valueOf() 、Double#valueOf()、Float#valueOf() 和 Boolean#valueOf() 的源码。

// Integer#valueOf()
public static Integer valueOf(int i) {
	if (i >= IntegerCache.low && i <= IntegerCache.high)
		return IntegerCache.cache[i + (-IntegerCache.low)];
	return new Integer(i);
}
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() {}
}
// Double#valueOf()
public static Double valueOf(String s) throws NumberFormatException {
	return new Double(parseDouble(s));
}
// Float#valueOf()
public static Float valueOf(String s) throws NumberFormatException {
	return new Float(parseFloat(s));
}
// Boolean#valueOf()
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);   
public static Boolean valueOf(boolean b) {
	return (b ? TRUE : FALSE);
}

对于 Integer,在 [-128, 127] 之间只有固定的 256 个值,所以为了避免多次创建对象,事先创建好一个大小为 256 的 Integer 数组 cache,所以如果值在这个范围内,就可以直接返回我们事先创建好的对象即可。

对于 Double 类型来说,我们就不能这样做,因为它在这个范围内个数是无限的。 总结一句就是:在某个范围内的整型数值的个数是有限的,而浮点数却不是。所以在 Double 里面的做法很直接,就是直接创建一个对象,所以每次创建的对象都不一样。

对于 Boolean 类型来说,在内部已经提前创建好两个对象,因为它只有两种情况,这样也是为了避免重复创建太多的对象。因此,每次执行 Boolean#valueOf() 返回的都是相同的对象。

总结

存在拆箱操作时一定要特别注意封装类对象是否为 null。

== 运算和算数运算时,会进行自动拆箱。

equals() 会进行自动装箱操作,且需要先判断封装类的类型是否相同,再进一步判断内容是否相同。

Integer、Short、Byte、Character、Long 这几个类的 valueOf() 的实现是类似的,均在存在 [-128, 127] 的缓存。

Double、Float 的 valueOf() 的实现是类似的,每次都返回不同的对象。

Boolean 预先创建了两个对象,Boolean#valueOf() 每次返回的都是相同的对象。

以上就是关于“Java自动装箱和拆箱”的介绍,大家如果想了解更多相关知识,不妨来关注一下动力节点的Java在线学习,里面的课程内容从入门到精通,很适合没有基础的小伙伴学习,相信对大家一定会有所帮助的。

提交申请后,顾问老师会电话与您沟通安排学习

免费课程推荐 >>
技术文档推荐 >>