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

KaelLi 2019年11月25日17:47:25
评论
3,5331

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

Integer的类声明

public final class Integer extends Number implements Comparable<Integer>

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

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

public abstract int intValue(); 返回当前对象数值的int型值
public abstract long longValue();返回当前对象数值的long型值
public abstract float floatValue();返回当前对象数值的float型值
public abstract double doubleValue();返回当前对象数值的double型值

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

public byte byteValue() {
    return (byte)intValue();
}

public short shortValue() {
    return (short)intValue();
}

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

Integer的属性字段

private final int value;

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

@Native public static final int   MIN_VALUE = 0x80000000;
@Native public static final int   MAX_VALUE = 0x7fffffff;

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

public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");

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

static final char[] digits = {
    '0' , '1' , '2' , '3' , '4' , '5' ,
    '6' , '7' , '8' , '9' , 'a' , 'b' ,
    'c' , 'd' , 'e' , 'f' , 'g' , 'h' ,
    'i' , 'j' , 'k' , 'l' , 'm' , 'n' ,
    'o' , 'p' , 'q' , 'r' , 's' , 't' ,
    'u' , 'v' , 'w' , 'x' , 'y' , 'z'
};

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

static final byte[] DigitTens = {
    '0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
    '1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
    '2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
    '3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
    '4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
    '5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
    '6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
    '7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
    '8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
    '9', '9', '9', '9', '9', '9', '9', '9', '9', '9',
    } ;

static final byte[] DigitOnes = {
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    } ;

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

static final int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
                                  99999999, 999999999, Integer.MAX_VALUE };

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

@Native public static final int SIZE = 32;

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

public static final int BYTES = SIZE / Byte.SIZE;

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

IntegerCache类:

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

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

构造方法

public Integer(int value)
public Integer(String s)

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

几个valueOf方法

public static Integer valueOf(int i)

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

@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

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

public static Integer valueOf(String s)
public static Integer valueOf(String s, int radix)

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

String转Integer的方法

public static int parseInt(String s, int radix)
public static int parseInt(CharSequence s, int beginIndex, int endIndex, int radix)
public static int parseInt(String s)
public static int parseUnsignedInt(String s, int radix)
public static int parseUnsignedInt(CharSequence s, int beginIndex, int endIndex, int radix)
public static int parseUnsignedInt(String s)
public static Integer decode(String nm)
public static Integer getInteger(String nm)
public static Integer getInteger(String nm, int val)
public static Integer getInteger(String nm, Integer val)

其中,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)的主要逻辑:

boolean negative = false;
int i = 0, len = s.length();
int limit = -Integer.MAX_VALUE;

if (len > 0) {
    char firstChar = s.charAt(0);
    if (firstChar < '0') { // Possible leading "+" or "-"
        if (firstChar == '-') {
            negative = true;
            limit = Integer.MIN_VALUE;
        } else if (firstChar != '+') {
            throw NumberFormatException.forInputString(s);
        }

        if (len == 1) { // Cannot have lone "+" or "-"
            throw NumberFormatException.forInputString(s);
        }
        i++;
    }
    int multmin = limit / radix;
    int result = 0;
    while (i < len) {
        // Accumulating negatively avoids surprises near MAX_VALUE
        int digit = Character.digit(s.charAt(i++), radix);
        if (digit < 0 || result < multmin) {
            throw NumberFormatException.forInputString(s);
        }
        result *= radix;
        if (result < limit + digit) {
            throw NumberFormatException.forInputString(s);
        }
        result -= digit;
    }
    return negative ? result : -result;
}

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

Integer转String的方法

public static String toString(int i, int radix)
public static String toUnsignedString(int i, int radix)
public static String toHexString(int i)
public static String toOctalString(int i)
public static String toBinaryString(int i)
public static String toString(int i)
public static String toUnsignedString(int i)

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

总结

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

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

KaelLi
  • 本文由 发表于 2019年11月25日17:47:25
  • 转载请务必保留本文链接:https://www.kaelli.com/42.html
匿名

发表评论

匿名网友 填写信息

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