OpenJDK源码阅读解析:Java11的Integer类源码分析详解

  • A+
所属分类:Java

Java中的基本数据类型有byte、short、int、long、float、double、boolean、char共8种,而它们每一种都对应了一个封装类,分别是Byte、Short、Integer、Long、Float、Double、Boolean、Character。这些封装类也是Java面向对象思想的重要体现,今天我就从中选择一个Integer类进行源码阅读解析,算是作为其中的一个代表吧。

Integer的类声明

Integer类是用final修饰的,说明它不可以被继承,对象也是不可变的,这一点跟String是一样的。

Integer继承自Number类,而Number是一个抽象类,继承它的类,都需要实现这样几个方法:

此外还提供了2个已经实现的方法:

分别返回对象数值的byte和short型值,看得出来都是利用intValue()方法的返回值进行强转的。

Integer的属性字段

这个就是Integer对象的值了,当然它肯定是一个整型了。需要注意的是它既是private又是final的,所以也是初始化后就不能改动的。有人说,不对啊,我Integer i = 0;,然后又i = 2,也没错啊?实际上这跟String非常像,所谓i的值变了实际上是把i指向了另外一个值为2的Integer对象。

分别定义了Integer的最大和最小值,最大值为2^31-1,最小值为-2^31。而@Native注解则说明这2个值有可能会被底层native代码所更改,所以在某些JVM上可能这2个值会有所改动。这里有个小问题,有兴趣的朋友可以自己想想为什么0x80000000的值就是-2^31,0x7fffffff的值就是2^31-1。

表示int这个基本类型的类的实例。说着可能有点绕,如果打印一下就知道,TYPE.getName()会返回”int”,实际上就是获得了int这个基本类型的名字。

digits数组里存放着用来把一个数字表示为字符串的所有可能用到的字符。这里可能有人(包括我)会比较费解,把一个整数表示为字符串,使用0-9以及A-F共16个字符不就足够了?怎么把26个字母全用上了?因为Integer类里会处理2-36进制的数,所以这个digits数组实际上包含了2进制到36进制的所有可能的字符。

这2个数组分别存放了从0到99的十位数字和个位数字,是方便计算的。比如你要获取99的十位数字和个位数字,直接到这2个数组里取就可以了。

数组的各个元素,分别是各个位数上最大的整数值了,用来判断一个整数的位数时比较方便。

表示一个int值在二进制形式下的位数,一般都是32位,而@Native则说明这个值可能会被JVM所更改。

表示一个int值在二进制形式下所占的字节数,一般都是4字节,但如果SIZE被所在Java虚拟机更改了,那么这个BYTES的值也可能发生改变。

IntegerCache类:

一个Integer的内部缓存类,很有意思,会把-128到127的Integer对象缓存起来,值在这个范围内的对象会被缓存起来。也就是说,实际上在该范围内的Integer对象,每个值都只有一份,即使你多次调用new Integer(100),也不会生成多个对象。

另外,从源码来看,这个缓存区间的最大值127是可以调整的,当然是通过jvm参数来设置的。

构造方法

这哥俩头上都已经被打上了Deprecated的标签,自从Java9之后这哥儿俩就不被推荐使用了,理由是用valueOf(int)和parseInt(String)会在空间和时间上有更好的性能表现。当然如果你目前还在使用Java8,那么是不会有什么影响的

几个valueOf方法

算是官方推荐的一个静态工厂方法了,既然是官方“商业吹捧”的东西,有必要看一下它的实现:

@HotSpotIntrinsicCandidate注解表示该方法特别重视性能,实际运行时,会用基于CPU指令的HotSpot高效率实现方式来代替Java源码的实现,也就是说实际运行中,这个方法的实现并不是这几行源码所表示的这样。当然源码我们也要看一下,因为总有一些环境运行的不是HotSpot环境的。源码的优化很简单,就是把刚才咱们说的IntegerCache缓存给用上了,在缓存区间内的值就不去生成额外的对象了,确实省时间省空间——当然到底有多大的效果就不得而知了,不过大部分情况下这个区间内的值用得会特别频繁,所以效果应该不错。

这2个方法放到一起看,都是先调用了parseInt(String s, int radix)方法,把字符串转成了一个int值,然后再调用valueOf(int i)得到了相应的Integer对象。

String转Integer的方法

其中,getInteger方法是比较特殊的,它是用来获取某个系统属性,其第一个String参数就是该系统属性的名字,千万不要理解为参数是”123”就会得到值为123的Integer对象哦。

decode(String nm)方法接收8进制、10进制和16进制的数字字符串,然后转成一个值为10进制的Integer对象。必须输入是“-010“,第一个0表示这是一个8进制数,结果得到的是-8。

几个parseInt方法大同小异,都是把字符串(或者字符序列)转成一个int值,然后通常会用这个值去得到一个相应的Integer对象。相对来说那3个无符号整型的方法不太常用。来看一下parseInt(String s, int radix)的主要逻辑:

逻辑还是比较直观的,以“-123“为例,其计算方式为,先把negative值设置为true,然后后面的数值是1*10*10+2*10+3,最后再根据negative的值来加上负号。limit为负值的原因不难得到,因为符号的问题,最小值是-2^31,而最大值则是2^31-1。

Integer转String的方法

提供了充足的Integer转字符串的方法,可以把给定的整数转成2进制、8进制、10进制和16进制的字符串,以及任何进制的字符串(进制不能超过给定的最小和最大进制的范围)。

总结

Java中的基本数据类型的封装类,就这样选择Integer作为代表来进行简单的分析。当然了,Integer类里还有不少其他工具型的方法,大部分是内部使用,不对外提供。感兴趣的同学,可以自己研究学习,感受一下写JDK的大牛们是如何使用小技巧的,包括(不限于)位运算、数组缓存、魔法数字等等。同时在Integer里也能看到之前在String源码里看到的COMPACT_STRINGS字段,换句话说在Java11里涉及到字符串处理的 基本上都要考虑是否开启字符串压缩的问题。

比较汗颜的是,距离上一篇Java源码解析的文章过去了9个月,汗颜啊汗颜……但也不得不承认,写这样的文章真的比较费时间(当然肯定不至于1年才写2篇)。接下来,应该好好想想下一步看哪一部分的源码了……

KaelLi

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: