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极悦在线学习,里面的课程内容从入门到精通,很适合没有基础的小伙伴学习,相信对大家一定会有所帮助的。
你适合学Java吗?4大专业测评方法
代码逻辑 吸收能力 技术学习能力 综合素质
先测评确定适合在学习