Java的自动装箱和拆箱都是针对的Java的基本数据类型。
1 Java的基本数据类型
Java 中有 8 种基本数据类型,如下表所示。
基本类型 | 位数 | 字节 | 默认值 | 取值范围 |
---|---|---|---|---|
byte | 8 | 1 | 0 | -128 ~ 127 |
short | 16 | 2 | 0 | -32768 ~ 32767 |
int | 32 | 4 | 0 | -2147483648 ~ 2147483647 |
long | 64 | 8 | 0L | -9223372036854775808 ~ 9223372036854775807 |
char | 16 | 2 | ‘u0000’ | 0 ~ 65535 |
float | 32 | 4 | 0f | 1.4E-45 ~ 3.4028235E38 |
double | 64 | 8 | 0d | 4.9E-324 ~ 1.7976931348623157E308 |
boolean | 1 | false | true、false |
另外对于 boolean,官方文档未明确定义,它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1 位,但是实际中会考虑计算机高效存储因素。同时值得注意的是:
- Java 里使用 long 类型的数据一定要在数值后面加上 L,否则将作为整型解析。
- 这八种基本类型都有对应的包装类分别为:
Byte、Short、Integer、Long、Float、Double、Character、Boolean
。 - 基本数据类型直接存放在 Java 虚拟机栈中的局部变量表中,而包装类型属于对象类型,对象实例都存在于堆中。相比于对象类型, 基本数据类型占用的空间非常小。
《Java 编程思想》:
Java 的每种基本类型所占存储空间的大小不会像其他大多数语言那样随机器硬件架构的变化而变化。这种所占存储空间大小的不变性是 Java 程序比用其他大多数语言编写的程序更具可移植性的原因之一。
2 包装类的常量池技术
Java 基本类型的包装类的大部分都实现了常量池技术。
2.1 Byte、Short、Integer、Long
把这四个包装类,分到一类的原因是:Byte、Short、Integer、Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据。
源码的话,Short和Long此部分源码逻辑相同,就不重复了。
Byte
:
private static class ByteCache {
private ByteCache(){}
static final Byte cache[] = new Byte[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Byte((byte)(i - 128));
}
}
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
Integer
:
private static class IntegerCache {
static final int low = -128;
static final int high;
static {
// high value may be configured by property
int h = 127;
...
}
}
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
Long
:
private static class LongCache {
private LongCache(){}
static final Long cache[] = new Long[-(-128) + 127 + 1];
static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
2.2 Character、Boolean
Character 创建了数值在 [0,127] 范围的缓存数据。Boolean 直接返回 True or False(也进行了缓存)。
Character
:
private static class CharacterCache {
private CharacterCache(){}
static final Character cache[] = new Character[127 + 1];
static {
for (int i = 0; i < cache.length; i++)
cache[i] = new Character((char)i);
}
}
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
Boolean
:
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
2.3 Float、Double
这两种浮点数类型的包装类 Float,Double 并没有实现常量池技术。
2.4 实战测试理解
Integer i1 = 22;
Integer i2 = 22;
System.out.println(i1 == i2);
// 输出 true
// 理由:常量池技术装箱
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4);
// 输出 false
// 理由:常量池技术只缓存了[-128, 127]的数据
Integer i5 = new Integer(22);
Integer i6 = new Integer(22);
System.out.println(i5 == i6);
// 输出 false
// i5和i6都创建了新对象,未利用常量池技术
Integer i7 = 40;
Integer i8 = new Integer(40);
System.out.println(i7 == i8);
// 输出 false
// i7返回的是常量池中的对象,i8创建了新对象
3 自动装箱与拆箱
- 装箱:将基本类型用它们对应的引用类型包装起来;
- 拆箱:将包装类型转换为基本数据类型;
Integer i = 10; //装箱
int n = i; //拆箱
上面这两行代码对应的字节码为:
L1
LINENUMBER 8 L1
ALOAD 0
BIPUSH 10
INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;
PUTFIELD AutoBoxTest.i : Ljava/lang/Integer;
L2
LINENUMBER 9 L2
ALOAD 0
ALOAD 0
GETFIELD AutoBoxTest.i : Ljava/lang/Integer;
INVOKEVIRTUAL java/lang/Integer.intValue ()I
PUTFIELD AutoBoxTest.n : I
RETURN
从字节码可以看出:
这里的装箱调用了Integer.valueOf()
方法。拆箱调用了Integer.intValue()
方法。
因此:
Integer i = 10
等价于Integer i = Integer.valueOf(10)
int n = i
等价于int n = i.intValue()
注意:如果频繁拆装箱的话,也会严重影响系统的性能。我们应该尽量避免不必要的拆装箱操作。