前言
Java 是一种面向对象的编程语言,Java 中的类把方法与数据类型连接在一起,构成了自包含式的处理单元。但在 Java 中不能定义基本类型对象,为了能将基本类型视为对象处理,并能连接相关方法,Java 为每个基本数据类型都提供了包装类,如 int 型数值的包装类 Integer,boolean 型数值的包装类 Boolean 等。这样便可以把这些基本类型转换为对象来处理了。
在Java中包含了8种基本数据类型,与之相对应的还有8种包装类,他们之间的对应关系如下:
基本数据类型
包装类
byte
Byte
short
Short
int
Integer
long
Long
float
Float
double
Double
boolean
Boolean
char
Character
什么是自动拆装箱
Java中不能定义基本数据类型的对象,因此我们可以使用包装类,每种基本数据类型都有自己对应的包装类。
基本数据类型与包装类之间的转换过程就涉及到了自动拆装箱。
基本数据类型转换为包装类的过程称作自动装箱
包装类转换为基本数据类型的过程称作自动拆箱
自动拆装箱的实现原理
举一个栗子:
1 2 3 4 5 6 7 8 9 public class AutoBoxing { public static void main (String[] args) { int i = 10 ; Integer ii = i; int iii = ii; } }
上面的代码实际上就是Java中的语法糖,通过对.class文件进行反编译之后就可以看到代码的真面目:
1 2 3 4 5 6 7 public class AutoBoxing { public static void main (String[] arrstring) { int n = 10 ; Integer n2 = Integer.valueOf(n); int n3 = n2.intValue(); } }
从反编译后的代码可以看到,int类型到Integer的装箱过程是通过Integer.valueOf()
实现,Integer到int的拆箱过程是通过intValue()
实现。
刚好我们测试下其他七种数据类型的拆装箱过程是怎么样的,代码如下AutoBox.java
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class AutoBox { public static void main (String[] args) { Integer aa = 10 ; int aaa = aa; Byte bb = 20 ; byte bbb = bb; Short cc = 30 ; short ccc = cc; Long d = 40L ; long dd = d; Float e = 50f ; float ee = e; Double f = 60 d; double ff = f; Character g = 'a' ; char gg = g; Boolean h = true ; boolean hh = h; } }
直接对AutoBox.java文件进行编译后,对AutoBox.class文件反编译分析,命令如下
//编译
javac AutoBox.java
//反编译分析
javap -c AutoBox.class
结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 Compiled from "AutoBox.java" public class com .zhengql .practice .autoBox .AutoBox { public com.zhengql.practice.autoBox.AutoBox(); Code: 0 : aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4 : return public static void main (java.lang.String[]) ; Code: 0 : bipush 10 2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 5 : astore_1 6 : aload_1 7: invokevirtual #3 // Method java/lang/Integer.intValue:()I 10 : istore_2 11 : bipush 20 13: invokestatic #4 // Method java/lang/Byte.valueOf:(B)Ljava/lang/Byte; 16 : astore_3 17 : aload_3 18: invokevirtual #5 // Method java/lang/Byte.byteValue:()B 21 : istore 4 23 : bipush 30 25: invokestatic #6 // Method java/lang/Short.valueOf:(S)Ljava/lang/Short; 28 : astore 5 30 : aload 5 32: invokevirtual #7 // Method java/lang/Short.shortValue:()S 35 : istore 6 37: ldc2_w #8 // long 40l 40: invokestatic #10 // Method java/lang/Long.valueOf:(J)Ljava/lang/Long; 43 : astore 7 45 : aload 7 47: invokevirtual #11 // Method java/lang/Long.longValue:()J 50 : lstore 8 52: ldc #12 // float 50.0f 54: invokestatic #13 // Method java/lang/Float.valueOf:(F)Ljava/lang/Float; 57 : astore 10 59 : aload 10 61: invokevirtual #14 // Method java/lang/Float.floatValue:()F 64 : fstore 11 66: ldc2_w #15 // double 60.0d 69: invokestatic #17 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double; 72 : astore 12 74 : aload 12 76: invokevirtual #18 // Method java/lang/Double.doubleValue:()D 79 : dstore 13 81 : bipush 97 83: invokestatic #19 // Method java/lang/Character.valueOf:(C)Ljava/lang/Character; 86 : astore 15 88 : aload 15 90: invokevirtual #20 // Method java/lang/Character.charValue:()C 93 : istore 16 95 : iconst_1 96: invokestatic #21 // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean; 99 : astore 17 101 : aload 17 103: invokevirtual #22 // Method java/lang/Boolean.booleanValue:()Z 106 : istore 18 108 : return }
经过测试,其他7种基本数据类型到包装类的装箱拆箱原理都与int/Integer相同,
自动装箱都是通过包装类的valueOf()方法来实现的,
自动拆箱都是通过包装类对象的xxxValue()来实现的
什么时候用到自动拆装箱
赋值操作时
包装类之间运算时(±*/)
1 2 3 Integer a = 1 ; Integer b = 2 ; int c = a + b;
比较运算时
1 2 Integer a=1 ; boolean b = a==1 ;
向集合中添加基本数据类型时
1 2 3 4 List<Integer> list = new ArrayList<>(); for (int i = 1 ; i < 10 ; i ++){ list.add(i); }
方法调用、参数返回时
1 2 3 4 5 6 7 8 9 10 11 public class AutoBox { private static int test (int i) { return i + 1 ; } public static void main (String[] args) { Integer i = 1 ; int a = test(i); Integer b = test(1 ); } }
装箱缓存
其实,在自动装箱过程中还存在一种缓存的操作,且看下面一道题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class AutoBoxTest { public static void main (String[] args) { Integer a = 30 ; Integer b = 30 ; if (a==b){ System.out.println("a、b:内存地址相同" ); }else { System.out.println("a、b:不同的两个对象" ); } Integer c = 300 ; Integer d = 300 ; if (c==d){ System.out.println("c、d:内存地址相同" ); }else { System.out.println("c、d:不同的两个对象" ); } } }
这道题乍一看是不是觉得匪夷所思,怎么会有这种沙雕题目,两个对象类型用等号判断大小,很明显都是new出来的对象,肯定指向不同的内存地址啊,肯定不相等了。然鹅运行的结果如下:
可以看到为什么同样的操作,c和d就符合判断逻辑,而a和b就偏偏指向同一个对象呢?
这是因为在自动装箱过程中,Integer对象通过使用相同的对象引用实现对象的缓存和重用。
那么问题又来了,既然有缓存操作,那为什么a、b有,c、d却没有呢?
来看一下Integer自动装箱的源码:
1 2 3 4 5 public static Integer valueOf (int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
首先判断入参i是否处于[IntegerCache.low,IntegerCache.high]
区间内,如果i值在区间内,则从缓存IntegerCache.cache
中读取某一个值返回,反之直接new一个Integer对象,这说明触发缓存操作是根据i值的范围决定的 。
那这个范围又是多少呢?阅读该方法的注释:
1 2 This method will always cache values in the range -128 to 127, inclusive, and may cache other values outside of this range.
此方法默认缓存[-128,127]
范围内的值,但也可以缓存范围外的其他值,这里是因为区间右侧的IntegerCache.high
是可配置的。
看到这里,终于明白,最开始的那道题目,为什么ab和cd的结果会完全不一样,是因为a、b的值在[-128,127]区间内,而c、d的值不在此范围内。
那么,既然Integer有缓存这个骚操作,那其他的包装类是不是也有呢?直接去看每个包装类的valueOf
方法就可以知道了。
这里我就不贴源码了,查看后的结论是,其他的7种包装类中,所有的整数类型的类,在自动装箱时都有类似于Integer的这种缓存操作,只不过他们各自的触发情况不同 ,结果整理如下:
包装类
缓存机制
触发条件
备注
Byte
ByteCache
[-128,127]
Short
ShortCache
[-128,127]
Integer
IntegerCache
[-128,127]
最大值可配置
Long
LongCache
[-128,127]
Float
-
-
Double
-
-
Boolean
-
-
Character
CharacterCache
[0,127]
总结
自动装箱和拆箱方便了我们开发人员,但是在使用自动拆装箱时也有很多翻车现场 ,最容易出现的就是空指针 ,所以在使用自动拆装箱时一定要防止空指针。
自动装箱过程中涉及到对象的创建等操作,如果在循环体中大量的拆装箱操作,势必会浪费资源 ,所以何时使用合理的使用自动拆装箱是尤为重要。
参考和感谢
Java中整型的缓存机制:https://www.hollischuang.com/archives/1174