Java 自动装箱与拆箱
转载:http://www.cnblogs.com/dolphin0520/p/3780005.html
时间:2023-2-9 21:31:17
1、自动装箱和拆箱是什么?
装箱:自动将【基本数据类型】转换成对应的【包装类】
拆箱:自动将【包装类】转换成对应的【基本数据类型】
// jkd1.5 之前想创建一个数值为 10 的 Integer 对象
Integer i = new Integer(10);
// jdk1.5 之后 会自动根据数值创建对应的包装类对象
// 装箱
Integer i = 10;
// 拆箱
int n = i;
下表是基本数据类型对应的包装类:
基本数据类型 | 包装类 |
---|---|
int(4字节) | Integer |
byte(1字节) | Byte |
short(2字节) | Short |
long(8字节) | Long |
float(4字节) | Float |
double(8字节) | Double |
char(2字节) | Character |
boolean(未定) | Boolean |
2、装箱和拆箱是如何实现的?
以 Integer <---> int 的自动装箱和拆箱为例:
public class IntegerTest {
public static void main(String[] args) {
// 自动装箱:int -> Integer
Integer i = 10;
// 自动拆箱: Integer -> int
int n = i;
}
}
使用 IDEA 插件(ASM Bytecode Outline)查看反编译后的字节码文件:
// class version 52.0 (52)
// access flags 0x21
public class oop/day14/IntegerTest {
// compiled from: IntegerTest.java
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Loop/day14/IntegerTest; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 5 L0
BIPUSH 10
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
ASTORE 1
L1
LINENUMBER 6 L1
ALOAD 1
INVOKEVIRTUAL java/lang/Integer.intValue ()I
ISTORE 2
L2
LINENUMBER 7 L2
RETURN
L3
LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
LOCALVARIABLE i Ljava/lang/Integer; L1 L3 1
LOCALVARIABLE n I L2 L3 2
MAXSTACK = 1
MAXLOCALS = 3
}
结论:从反编译得到的字节码内容可以看出,在装箱的时候自动调用的是 Integer.valueOf()
方法。而在拆箱的时候自动调用的是 obj.intValue()
方法。
其他的也类似,比如: Double、Character
因此可以用一句话总结装箱和拆箱的实现过程:
装箱过程是通过调用包装类的 valueOf
方法实现的,而拆箱过程是通过调用包装类的 xxxValue
方法实现的(xxx 代表对应的基本数据类型)。
3、面试中相关问题
3.1、下面这段代码(Integer 类)的输出结果是什么?
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);
System.out.println(i3==i4);
结果:
true
false
输出结果表明 i1 和 i2 指向的是同一个对象,而 i3 和 i4 指向的是不同的对象。
此时只需一看源码便知究竟:
Integer.valueOf(int i) 源码
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
IntegerCache 类源码
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() {}
}
结论:在通过 valueOf 方法创建 Integer 对象的时候,如果数值在[-128, 127] 之间,便返回指向 IntegerCache.cache 中已经存在的对象的引用;否则创建一个新的 Integer 对象。
上面的代码中 i1 和 i2 的数值为 100,因此会直接从 cache 中取已经存在的对象,所以 i1 和 i2 指向的是同一个对象,而 i3 和 i4 则是分别指向不同的对象。
3.2、下面这段代码(Double类)的输出结果是什么?
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
System.out.println(i1 == i2);
System.out.println(i3 == i4);
结果:
false
false
Double.valueOf(double d) 源码
public static Double valueOf(double d) {
return new Double(d);
}
为什么 Double.valueOf 方法会采用与 Integer.valueOf 方法不同的实现?
因为在某个范围内的整型数值的个数是有限的,而浮点数却不是。
注:
Byte、Short、Character、Integer、Long 这 5 个包装类的 valueOf 方法的实现是类似的。
Double、Float 的 valueOf 方法的实现是类似的。
3.3、下面这段代码(Boolean类)的输出结果是什么?
Boolean i1 = false;
Boolean i2 = false;
Boolean i3 = true;
Boolean i4 = true;
System.out.println(i1==i2);
System.out.println(i3==i4);
结果:
true
true
Boolean.valueOf(boolean b) 源码
// TRUE FALSE 为类变量
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);
}
3.4、谈谈 Integer i = new Integer(xxx); 和 Integer i = xxx; 这两种方式的区别?
- 第一种方式不会触发自动装箱的过程;而第二种方式会触发;
- 在执行效率和资源占用上的区别: 第二种方式的执行效率和资源占用在一般性情况下要优于第一种情况(注意这并不是绝对的)
3.5、下面这段代码(Integer 和 Long 类)的输出结果是什么?
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L;
System.out.println(c == d);
System.out.println(e == f);
System.out.println(c == (a + b));
System.out.println(c.equals(a + b));
System.out.println(g == (a + b));
System.out.println(g.equals(a + b));
System.out.println(g.equals(a + h));
结果:
true
false
true
true
true
false
true
当 "==" 运算符的两个操作数都是同包装类类型的引用,则比较的是引用是否是同一个对象
否则若其中有一个操作数是表达式(即包含算术运算)或者是基本数据类型,则比较的是数值(即会触发自动拆箱的过程)
第一个和第二个输出结果没有什么疑问。
第三句由于 a+b 包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),因此它们比较的是数值是否相等。
而对于 c.equals(a + b) 会先触发自动拆箱过程,再触发自动装箱过程,也就是说 a + b,会先各自调用 intValue 方法,得到了加法运算后的数值之后,便调用 Integer.valueOf 方法,再进行equals比较。
同理对于后面的也是这样,不过要注意倒数第二个和最后一个输出的结果
(如果数值是int类型的,装箱过程调用的是Integer.valueOf;如果是long类型的,装箱调用的Long.valueOf方法)。
Integer a = 1;
Integer b = 2;
Long g = 3L;
Long h = 2L;
// Integer + Integer (拆箱)-> int + int (装箱)-> Integer
System.out.println(g.equals(a + b)); // false
// Integer + Long -> int + long -> long + long -> Long + Long
System.out.println(g.equals(a + h)); // true
包装类 Long 中 equals(Object obj) 方法的源码 与 Integer 中类似
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!