一文带你深入了解Java的自动拆装箱
作者:荼锦AI编程
什么是自动拆装箱
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类型作为运算类型来进行的
例如,下面代码每一行代码上面的注释为其部分字节码指令
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
public class Test { public static void main(String[] args) { Byte num = 1; byte num3 = num; } }
编译后代码
public class Test { public static void main(String[] args) { Byte num = Byte.valueOf(1); byte num3 = num.byteValue(); } }
从编译后代码可以看出,这两者的转换用了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)); } } 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
public class Test { public static void main(String[] args) { Integer num = 1; Integer num2 = 200; int num3 = num; } }
编译后代码
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对象
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
同样是这两个方法,由于布尔型只有两个值,所以直接缓存两个对象
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,..................,数量太多所以没有某一个浮点数会被反复使用,所以没必要缓存,直接返回一个Float新对象
public static Float valueOf(float f) { return new Float(f); } public static Double valueOf(double d) { return new Double(d); }
测试
我们测试一下是否返回的是同一个对象
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
public class Test { public static void main(String[] args) { Integer b1 = 1000; Integer b2 = 1000; System.out.println(b1 == b2);// false } }
而1000这个数值已经没有缓存了,所以返回的是两个对象
public class Test { public static void main(String[] args) { Double b1 = 10.0; Double b2 = 10.0; System.out.println(b1 == b2);// false } }
由于Double是没有用缓存的,所以两个对象一定是不同的
到此这篇关于一文带你深入了解Java的自动拆装箱的文章就介绍到这了,更多相关Java自动拆装箱内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!