您的位置 首页 java

Java基础之神奇的包装类(二)

1. 导读

上期分享了个人对于包装类的一些理解, 这期还是围绕着下面三个问题来展开:

.1 IntegerCache做了什么? 为什么需要IntegerCache?

.2 自动拆装箱;

.3 为什么 == 和 equals的结果有时候相同, 有时候却不同?

2. IntegerCache

八个基本数据类型的封装类除了Double 和 Float 以外, 都是有各自的Cache, 我们还是以比较特殊的IntegerCache来举例:

.1 IntegerCache先定义 缓存 的最小值是-128, 而最大值只是声明了, 具体赋值放到了static代码块中处理;
 

.2 static代码块干的第一件事就是给high赋值, 默认是127, 但是这个最大值可以JVM的-XX:AutoBoxCacheMax参数设置; 如果设置的值比127小, 那么默认值就是127, 但是如果设置的值+129比int的最大值还大, 那就使用int最大值-129;

.3 static代码块干的第二件事是用for循环把[-128, high]之间的int值包装成Integer对象放到cache[]中;

我们一般不会修改JVM的AutoBoxCacheMax的参数, 所以IntegerCache缓存的是[-128, 127]之间的整数, 也就引出了第二个问题, 为什么缓存的是[-128, 127]?

3. 为什么是[-128, 127]

既然IntegerCache是用作缓存的, 那为什么是缓存了[-128, 127]之间的整数, 为什么不缓存[0, 255]或者[0, MAX_INTEGER]呢?

这个问题的答案在 JAVA Language Specification 中的5.1.7中已经做了说明:

用普通话来说就是 如果是基本类型的封装类, 要保证 Byte 和 Character的值在\u0000(0), \u007f(127), Integer 和 Short的值在[-128, 127]之间的值, 必须保证使用 == 判断永远为真;

使用 == 判断的引用的地址是否相同, 要实现上面的规定, 在[-128, 127]之间的Integer引用的都是同一个内存对象才可以, 所以IntegerCache就将[-128, 127]之间的值类初始化的时候就缓存起来了, [-128, 127]之间的值在转换为Integer时, 会从IntegerCache中获取对象, 而不是重新生成一个新的对象;

虽然是这样规定的, 但是设计这个规定的初衷是什么呢? 先说IntegerCache运用了一种设计模式—-享元模式;享元模式是通过复用相同对象来解决对象过多带来的性能下降的问题; 所以IntegerCache其实就是通过缓存来换取运行效率, 也就是通过空间换取时间; 那么IntegerCache为什么设大一点呢? 这样缓存的越多, 速度不就更快吗?

我们从结果推论, [-128, 127]由于符号位的存在, 刚好是一位(8个字节)所能表示的范围区间, 所以个人猜测这可能是一个原因; 如果读者朋友还有其他考虑, 欢迎在评论中交流;

LongCache, ShortCache, ByteCache和Integer唯一的区别就是[-128, 127]这个区间是写死的; CharacterCache则是[0, 127];

Double 和 Float是没有缓存的, 主要是因为浮点数重复的概率比较小, 就没必要缓存了;

4. 自动拆装箱

 Integer  number  = 1;
 

我们来解析上面的代码: 把整数1赋值给Integer对象number; 看到这里, 是否有个疑惑, 左边是引用类型, 要给引用类型赋值, 但是右边却是一个基本数据类型, 那么就变成了把一个基本数据类型赋值给了引用类型, 这明显违背我们对于JAVA的认知啊; 所以在编译的时候, 编译器会把代码编程下面的样子:

 Integer number = Integer.valueOf(1);
 

这样是不是就看着舒服多了; 这个操作就是自动装箱, 调用了Integer::valueOf;

那么自动拆箱也就很好理解了, 就是自动装箱的反操作:

 Integer number = 1;
 int numberTwo = number;
 

表面上看起来就是把引用类型赋值给基本数据类型, 但是编译器会编译成下面的代码:

 Integer number = Integer.valueOf(1);
 int numberTwo = number.intValue();
 

所以自动拆箱操作其实是调用了Integer::intValue;

注意点:

.1 Integer::valueOf 也是先从IntegerCache获取对象的, 需要特别注意[-128, 127]的整数;

.2 自动拆箱底层调用的Integer::intValue, 需要对调用对象进行判空, 否则会导致NPE异常;

.3 其他几种基本类型的包装类进行的拆装箱操作其实都是一样的: xx.valueOf进行装箱操作, xx.xxxValue进行拆箱操作;

.4 >=, >, <等操作都会进行自动拆箱操作, 编码时需要特别注意这种隐式拆箱引起的NPE;

5. 为什么 == 和 equals 判断会有不同;

Integer::equals的源码以及作用在前面已经说过了, 那么加上今天的IntegerCache的知识, 就可以解答这个问题了;

.1 因为[-128, 127]之间的整数都是返回的同一个对象, 所以 == 和 equals的结果是一样的;

.2 如果比较的Integer的值并不在[-128, 127]之间, 那么因为比较的是两个不同的对象, == 比较的是 堆内存 地址, 自然不同, 但是equals比较的是Integer.value, 如果两个值一样, 那么就会出现 == 和 equals不同的情况了;

.3 Integer等包装类的比较建议使用equals 和 compareTo, 不仅更符合面向对象的编程方式, 也避免了IntegerCache引起的问题;

以上就是今天分享的主要内容, JAVA中八大基本类型的封装类就介绍到这里, 如果有什么不足之处, 欢迎指正; 如果觉得看了有帮助, 烦请点赞并转发, 谢谢;

文章来源:智云一二三科技

文章标题:Java基础之神奇的包装类(二)

文章地址:https://www.zhihuclub.com/188742.shtml

关于作者: 智云科技

热门文章

网站地图