什么是自动拆装箱
JDK 1.5开始增加了自动拆装箱机制,Java保留了一些原始的基本数据类型,但由于Java是强面向对象语言,数据类型当然也应该设置成对象才是,所以Java也推出了对于基本数据类型的对应的对象,将基本数据类型转换为对象就称为装箱,反之则是拆箱
八种基本数据类型
基本数据类型 | 对象类型 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
Java的大部分字节码指令都没有支持类型byte、char和short,甚至没有任何指令支持boolean类型。
那么Java底层是如何实现这些数据类型的?
事实上编译器会在编译期或运行期搞事
- 将byte和short类型的数据带符号扩展(Sign-Extend)为相应的int类型数据。
- 将boolean和char类型数据零位扩展(Zero-Extend)为相应的int类型数据。
在处理boolean、byte、short和char类型的数组时,也会转换为使用对应的int类型的字节码指令来处理。因此,大多数对于boolean、byte、short和char类型数据的操作,实际上都是使用相应的对int类型作为运算类型来进行的
例如,下面代码每一行代码上面的注释为其部分字节码指令
java
public class Test {
/*
* 以下均为整型入栈指令
* iconst_1 (-1到5的数据使用iconst指令,-1使用iconst_m1指令)
* bipush 100 (-128到127[一个字节]的数据使用bipush指令)
* sipush 1000 (-32768到32767[二个字节]的数据使用sipush指令)
* ldc 40000 (-2147483648到2147483647[四个字节]的数据使用ldc指令)
*/
public static void main(String[] args) {
// bipush 100【将整型数据100压入栈】
byte b1 = 100;
// bipush 101【将整型数据101压入栈】
short s1 = 101;
// bipush 49【将整型数据49压入栈】
char c1 = '1';
// iconst_1【将整型数据1压入栈】
boolean bl1 = false;
}
}
自动拆装箱原理
byte与Byte
java
public class Test {
public static void main(String[] args) {
Byte num = 1;
byte num3 = num;
}
}
编译后代码
java
public class Test {
public static void main(String[] args) {
Byte num = Byte.valueOf(1);
byte num3 = num.byteValue();
}
}
从编译后代码可以看出,这两者的转换用了Byte类中的方法,看一下这两个方法
java
// 静态内部类
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));
}
}
private final byte value;
public static Byte valueOf(byte b) {
// 数组是不存在负的下标的,所以加上偏移量
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
public Byte(byte value) {
this.value = value;
}
public byte byteValue() {
return value;
}
Byte类在使用的时候就会将[-128,127]范围的Byte对象缓存进类中,这是用了享元模式的思想存取常用的热点Byte对象,重复利用该范围类的Byte对象,也就是说在这个数值范围的Byte对象一直是同一个对象
short与Short、long与Long、char与Character的转换原理和这个相同
(char保存的是ASCII码,所以只缓存[0,127]的对象)
int与Integer
java
public class Test {
public static void main(String[] args) {
Integer num = 1;
Integer num2 = 200;
int num3 = num;
}
}
编译后代码
java
public class Test {
public static void main(String[] args) {
Integer num = Integer.valueOf(1);
Integer num2 = Integer.valueOf(200);
int num3 = num.intValue();
}
}
我们可以看出自动拆装箱事实上是调用了Integer类中的方法,来看一下这两个方法,事实上和byte类似,默认[-128,127]的Integer对象被缓存了,但是IntegerCache.high是可能被人为配置过的,如果配置的缓存上限值大于127就会缓存[-128,配置的上限值],在此范围内直接返回缓存的对象,否则就new一个新的Integer对象
java
public static Integer valueOf(int i) {
// IntegerCache.low = -128,IntegerCache.high = 127(默认)
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
boolean与Boolean
同样是这两个方法,由于布尔型只有两个值,所以直接缓存两个对象
java
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
private final boolean value;
public Boolean(boolean value) {
this.value = value;
}
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
public boolean booleanValue() {
return value;
}
float与Float以及double与Double
由于浮点数整数位相同时小数位可以有多种情况,比如整数位为1的有1.321332,1.543635, <math xmlns="http://www.w3.org/1998/Math/MathML"> . . . . . . ...... </math>......,数量太多所以没有某一个浮点数会被反复使用,所以没必要缓存,直接返回一个Float新对象
java
public static Float valueOf(float f) {
return new Float(f);
}
public static Double valueOf(double d) {
return new Double(d);
}
测试
我们测试一下是否返回的是同一个对象
java
public class Test {
public static void main(String[] args) {
Integer b1 = 10;
Integer b2 = 10;
Integer b3 = new Integer(10);
Integer b4 = new Integer(10);
System.out.println(b1 == b2); // true
System.out.println(b2 == b3); // false
System.out.println(b3 == b4); // false
}
}
引用类型使用等于比较,比较的是对象是否相同(基本数据类型使用 = 进行比较比较的是数值)
那么b1等于b2说明两个装箱类型返回的b1和b2两个Integer对象是同一个对象,10这个数值在缓存范围内
而使用构造方法返回的对象是两个不同的对象,所以不相同结果为false
java
public class Test {
public static void main(String[] args) {
Integer b1 = 1000;
Integer b2 = 1000;
System.out.println(b1 == b2);// false
}
}
而1000这个数值已经没有缓存了,所以返回的是两个对象
java
public class Test {
public static void main(String[] args) {
Double b1 = 10.0;
Double b2 = 10.0;
System.out.println(b1 == b2);// false
}
}
由于Double是没有用缓存的,所以两个对象一定是不同的
如果您觉得该文章有用,欢迎点赞、留言并分享给更多人。感谢您的支持!