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 | public class AutoBoxing { |
上面的代码实际上就是Java中的语法糖,通过对.class文件进行反编译之后就可以看到代码的真面目:
1 | public class AutoBoxing { |
从反编译后的代码可以看到,int类型到Integer的装箱过程是通过Integer.valueOf()实现,Integer到int的拆箱过程是通过intValue()实现。
刚好我们测试下其他七种数据类型的拆装箱过程是怎么样的,代码如下AutoBox.java:
1 | public class AutoBox { |
直接对AutoBox.java文件进行编译后,对AutoBox.class文件反编译分析,命令如下
//编译
javac AutoBox.java
//反编译分析
javap -c AutoBox.class
结果如下
1 | Compiled from "AutoBox.java" |
经过测试,其他7种基本数据类型到包装类的装箱拆箱原理都与int/Integer相同,
自动装箱都是通过包装类的valueOf()方法来实现的,
自动拆箱都是通过包装类对象的xxxValue()来实现的
1 | Integer a = 1; |
1 | Integer a = 1; |
1 | Integer a=1; |
1 | List<Integer> list = new ArrayList<>(); |
1 | public class AutoBox { |
其实,在自动装箱过程中还存在一种缓存的操作,且看下面一道题:
1 | public class AutoBoxTest { |
这道题乍一看是不是觉得匪夷所思,怎么会有这种沙雕题目,两个对象类型用等号判断大小,很明显都是new出来的对象,肯定指向不同的内存地址啊,肯定不相等了。然鹅运行的结果如下:
1 | a、b:内存地址相同 |
可以看到为什么同样的操作,c和d就符合判断逻辑,而a和b就偏偏指向同一个对象呢?
这是因为在自动装箱过程中,Integer对象通过使用相同的对象引用实现对象的缓存和重用。
那么问题又来了,既然有缓存操作,那为什么a、b有,c、d却没有呢?
来看一下Integer自动装箱的源码:
1 | public static Integer valueOf(int i) { |
首先判断入参i是否处于[IntegerCache.low,IntegerCache.high]区间内,如果i值在区间内,则从缓存IntegerCache.cache中读取某一个值返回,反之直接new一个Integer对象,这说明触发缓存操作是根据i值的范围决定的。
那这个范围又是多少呢?阅读该方法的注释:
1 | This method will always cache values in the range -128 to 127, |
此方法默认缓存[-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
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 | public class AutoBoxing { |
上面的代码实际上就是Java中的语法糖,通过对.class文件进行反编译之后就可以看到代码的真面目:
1 | public class AutoBoxing { |
从反编译后的代码可以看到,int类型到Integer的装箱过程是通过Integer.valueOf()实现,Integer到int的拆箱过程是通过intValue()实现。
刚好我们测试下其他七种数据类型的拆装箱过程是怎么样的,代码如下AutoBox.java:
1 | public class AutoBox { |
直接对AutoBox.java文件进行编译后,对AutoBox.class文件反编译分析,命令如下
//编译
javac AutoBox.java
//反编译分析
javap -c AutoBox.class
结果如下
1 | Compiled from "AutoBox.java" |
经过测试,其他7种基本数据类型到包装类的装箱拆箱原理都与int/Integer相同,
自动装箱都是通过包装类的valueOf()方法来实现的,
自动拆箱都是通过包装类对象的xxxValue()来实现的
1 | Integer a = 1; |
1 | Integer a = 1; |
1 | Integer a=1; |
1 | List<Integer> list = new ArrayList<>(); |
1 | public class AutoBox { |
其实,在自动装箱过程中还存在一种缓存的操作,且看下面一道题:
1 | public class AutoBoxTest { |
这道题乍一看是不是觉得匪夷所思,怎么会有这种沙雕题目,两个对象类型用等号判断大小,很明显都是new出来的对象,肯定指向不同的内存地址啊,肯定不相等了。然鹅运行的结果如下:
1 | a、b:内存地址相同 |
可以看到为什么同样的操作,c和d就符合判断逻辑,而a和b就偏偏指向同一个对象呢?
这是因为在自动装箱过程中,Integer对象通过使用相同的对象引用实现对象的缓存和重用。
那么问题又来了,既然有缓存操作,那为什么a、b有,c、d却没有呢?
来看一下Integer自动装箱的源码:
1 | public static Integer valueOf(int i) { |
首先判断入参i是否处于[IntegerCache.low,IntegerCache.high]区间内,如果i值在区间内,则从缓存IntegerCache.cache中读取某一个值返回,反之直接new一个Integer对象,这说明触发缓存操作是根据i值的范围决定的。
那这个范围又是多少呢?阅读该方法的注释:
1 | This method will always cache values in the range -128 to 127, |
此方法默认缓存[-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