装箱(Boxing),也称为包装(Wrapper),是在对象中放置原语类型(primitive type)的过程,以便原语(primitive)可以作为引用对象使用。
这里的primitive type
就是Java里面的基本类型,所有的基本类型都有一个与之对应的类。例如,Integer类对应基本类型int。
通常,这些类称为包装器(wrapper)。这些对象包装器类拥有很明显的名字:Integer、Long、Float、Double、Short、Byte、Character、Void和Boolean(前6个类派生于公共的超类Number)。
对象包装器类是不可变的,即一旦构造了包装器,就不允许更改包装在其中的值。同时,对象包装器类还是final,因此不能定义它们的子类。
自动装箱
是指通过类型转换(隐式或显式)从值类型中获取引用类型,这部分工作是编译器
帮我们来完成的。
我们看一个常见的例子,比如我们创建一个int类型的ArrayList(因为ArrayList的泛型是不允许基本类型的,这里只能使用它们包装类),我们给ArrayList添加元素,再从里面获取元素,一般是这么写的:
// int类型的自动装箱和拆箱
ArrayList integerArrayList = new ArrayList<>();
integerArrayList.add(1);
int i = integerArrayList.get(0);
这里分别触发了自动装箱和自动拆箱,这里的add操作触发了一次自动装箱
操作,将int转化为Integer;接着从ArrayList里面获取元素,由于我们的目标变量类型是基本类型int,但获取到的元素类型是Integer,所以编译器
在这里帮我们做了拆箱
的操作。
我们经常说自动装箱、自动拆箱,到底是如何个自动法,我们来一个眼见为实,通过查看java代码生成的字节码来看下编译器对我们的代码做了什么。
这里介绍两种查看字节码的方式:
第一种,通过javac和javap查看:先通过javac将.java代码编译成.class字节码,然后通过javap分析字节码。
(base) tinytongtongdeMacBook-Pro% javac TestAutoWrapper.java
(base) tinytongtongdeMacBook-Pro% javap -verbose TestAutoWrapper
这样你就能看到你的字节码信息了。
第二种,通过IDE插件ASM Bytecode Outline来查看,具体操作方式见插件说明。
public static void main(String[] args) {
// int类型的自动装箱和拆箱
ArrayList integerArrayList = new ArrayList<>();
integerArrayList.add(1);// 自动装箱
int i = integerArrayList.get(0);// 自动拆箱
}
我们生成这段java代码的字节码,核心部分如下:
// access flags 0x9
public static main([Ljava/lang/String;)V
...
L1
LINENUMBER 15 L1
ALOAD 1
ICONST_1
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z
POP
L2
LINENUMBER 16 L2
ALOAD 1
ICONST_0
INVOKEVIRTUAL java/util/ArrayList.get (I)Ljava/lang/Object;
CHECKCAST java/lang/Integer
INVOKEVIRTUAL java/lang/Integer.intValue ()I
ISTORE 2
...
L1部分中的倒数第二行,INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z
,INVOKEVIRTUAL
指令表示一个虚方法调用,这里具体就是我们java代码中的integerArrayList.add(1);
,自动装箱发生在哪呢?就在它上面,INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
,INVOKESTATIC
表示静态方法调用,这里对应的Java语句就是Integer#valueOf()方法。
接下来我们看下自动装箱对应的字节码,也就是L2部分,先看INVOKEVIRTUAL java/util/ArrayList.get (I)Ljava/lang/Object;
,它对应的java代码是integerArrayList.get(0)
,表示从ArrayList里面获取到Integer类型对象。自动拆箱发生在下面,就是NVOKEVIRTUAL java/lang/Integer.intValue ()I
这条指令,它对应的java方法是Integer#intValue()方法。
看到这里相信大家对自动装箱
和拆箱
有一个比较具体的认识了,说白了就是编译器会根据情况替我们做一些工作,通过插入字节码指令来替我们完成装箱和拆箱操作。int对应的装箱方法是Integer#valueOf,拆箱方法是Integer#intValue()。
我们接着讲下自动装箱和拆箱的触发时机,具体如下:
* 进行 = 赋值操作(装箱或拆箱)
* 进行+,-,*,/混合运算 (拆箱)
* 进行>,<,==比较运算(拆箱)
* 调用equals进行比较(装箱)
* ArrayList,HashMap等集合类 添加基础类型数据时(装箱)
基本类型 | 包装类型 | 装箱方法 | 拆箱方法 |
---|---|---|---|
int | Integer | Integer#valueOf() | Integer#intValue() |
long | Long | Long.valueOf() | Long.longValue() |
byte | Byte | Byte.valueOf() | Byte.byteValue() |
short | Short | Short.valueOf() | Short.shortValue() |
float | Float | Float.valueOf() | Float.floatValue() |
double | Double | Double.valueOf() | Double.doubleValue() |
char | Character | Character.valueOf() | Character.charValue() |
boolean | Boolean | Boolean.valueOf() | Boolean.booleanValue() |
感兴趣的同学可以自己试下各种操作下,自动装箱和拆箱的表现,查看对应的字节码即可。
Wikiedia:Object type (object-oriented programming)
5分钟彻底理解-Java自动装箱、拆箱