本片文章主要介绍 Integer 类中一些比较常用的方法,并分析该类在 Java 中的作用。

总览

java.lang 下的 Integer 类是我们平时经常使用的类,首先看一下该类的继承或实现关系,如下图所示:

image.png

可以看到,Integer 类继承了 Number 这一抽象类,又实现了 Comparable 接口。

抽象类 NumberBigDecimalBigIntegerByteDoubleFloatIntegerLong 以及 Short 类的父类。

Number 类的子类必须提供将所表示的数值转换为 bytedoublefloatintlong 以及 short 的方法。

Comparable 接口中只有一个 compareTo(T o) 方法,用于比较元素之间的大小。

属性

Integer 类中的重要属性(域)如下所示:

image.png

其中:

  • MIN_VALUE = 0x80000000 表示最小的常数值为 $ -2^{31} $,即 -2147483648;
  • MAX_VALUE = 0x7fffffff 表示最大的常数值为 $ 2^{31}-1 $,即 2147483647;
  • SIZE 用于表示 int 的二进制补码形式的值的比特位数;
  • SIZES 用于表示 int 的二进制补码形式的字节数,值为 SIZE / Byte.SIZE = 32 / 8,即等于 4
  • public static final Class<Integer> TYPE 表示原始类型 int 的 Class 实例。

此外,DigitTensDigitOnes如下所示:这俩用于获取从 0 到 99 之间某个数的 个位十位。例如 12,从 DigitOnes 中取出 2,而从 DigitTens 中取出 1

 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
final static char [] 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',
    } ;

final static char [] 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',
    } ;

digits 用于将数字表示为字符串的所有可能字符,如下所示:

1
2
3
4
5
6
7
8
final static 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'
};

sizeTable 属性与 stringSize(int x) 方法一起使用。它们用于计算一个 int 型的数字所对应字符串的长度,例如 12 所对应的字符串的长度就是 2,而 123 所对应的字符串的长度就是 3。

一般我们在计算一个数字有几位的时候,通常使用求余或除法进行计算,但这里使用了一个 sizeTable 数组来进行实现,也可以达到求位数的目的。

1
2
3
4
5
6
7
8
9
final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
                                    99999999, 999999999, Integer.MAX_VALUE };

// Requires positive x
static int stringSize(int x) {
    for (int i=0; ; i++)
        if (x <= sizeTable[i])
            return i+1;
}

内部类 IntegerCache

IntegerCache 作为 Integer 的内部类,从命名方式就可以很清晰的看出,它主要和 缓存 有关。先看源码:

 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
private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // high value may be configured by property
            int h = 127;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);
                    // Maximum array size is Integer.MAX_VALUE
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                    // If the property cannot be parsed into an int, ignore it.
                }
            }
            high = h;

            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            // range [-128, 127] must be interned (JLS7 5.1.7)
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }
  • 从第 4 行可以看到,该缓存是使用Integer类型的cache[]数组实现的,默认范围是[-128, 127]
  • 从第 23 到第 26 行可以看出,默认实例化了 256 个 Integer 对象。

为什么需要缓存呢?

从文章开头你可以看到,int 的范围很大,将它们全部缓存起来的话代价很高。所以,当有一个新的 Integer 对象处于 [-128, 127] 中的时候,直接从缓存数组 cache[] 中取就好了,不用再重新实例化了,这样可以减小系统的开销。此外,在第 7 行的注释也说了,h 的值也可以通过设置虚拟机参数进行更改,即 Djava.lang.Integer.IntegerCache.high=xxx

常用方法

构造方法

两个构造方法如下所示:

1
2
3
4
5
6
7
public Integer(int value) {
    this.value = value;
}

public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
}

至于 parseInt() 方法是什么,我们接着往下看。

parseInt() 方法

这里有两个 parseInt() 方法,这里主要看带有两个参数的方法即可,如下所示:

 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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 第一个 parseInt 方法
public static int parseInt(String s) throws NumberFormatException {
    // 将字符串 s 以十进制输出
    return parseInt(s, 10);
}

// 第二个 parseInt 方法(主要看这个)
public static int parseInt(String s, int radix) throws NumberFormatException {
    // 判断字符串 s 是否为空
    if (s == null) {
        throw new NumberFormatException("null");
    }
    // 若转换的进制数小于 2,则抛出异常
    // 因为 Character.MIN_RADIX = 2
    if (radix < Character.MIN_RADIX) {
        throw new NumberFormatException("radix " + radix +
                                        " less than Character.MIN_RADIX");
    }
    // 若转换的进制数大于 36, 则抛出异常
    // 因为 Character.MAX_RADIX = 36,从 0 到 9 一共 10 位,a 到 z 一共 26 位,总共 36 位 
    if (radix > Character.MAX_RADIX) {
        throw new NumberFormatException("radix " + radix +
                                        " greater than Character.MAX_RADIX");
    }

    int result = 0;
    boolean negative = false;
    int i = 0, len = s.length();
    // limit = -2147483647
    int limit = -Integer.MAX_VALUE;
    int multmin;
    int digit;

    // 如果待转换的字符串 s 的长度大于 0
    if (len > 0) {
        // 取出字符串 s 的第一个字符,从而进行判断是 “+” 还是 “-”,
        // 因为这两个字符的 ASCII 码都是小于字符 '0' 的 
        char firstChar = s.charAt(0);
        if (firstChar < '0') { 
            // 如果字符串 s 的第一个字符是 '-'
            if (firstChar == '-') {
                negative = true;
                limit = Integer.MIN_VALUE;
            } else if (firstChar != '+')
                throw NumberFormatException.forInputString(s);

            // 待转换字符串 s 的长度是 1,不能是单独的 “+” 或 “-”
            if (len == 1) 
                throw NumberFormatException.forInputString(s);
            i++;
        }
        multmin = limit / radix;
        // 除了第一个字符以外,从左到右不断遍历每个字符,根据进制不断相乘再相加,从而得到结果
        // 例如:parseInt("13ab", 16),则计算方式为 1*(16的3次方)+3*(16的2次方)+10*(16的1次方)+11*(16的0次方)=5035
        //       parseInt("123", 8),则计算方式为 1*(8的2次方)+2*(8的1次方)+*(8的2次方)=83
        while (i < len) {
            digit = Character.digit(s.charAt(i++),radix);
            if (digit < 0) {
                throw NumberFormatException.forInputString(s);
            }
            if (result < multmin) {
                throw NumberFormatException.forInputString(s);
            }
            result *= radix;
            if (result < limit + digit) {
                throw NumberFormatException.forInputString(s);
            }
            result -= digit;
        }
    } else {
        throw NumberFormatException.forInputString(s);
    }
    // 根据第一个字符是否为负号 “-”,将结果转为负数
    return negative ? result : -result;
}

parseInt(String s, int radix) 方法与《剑指 Offer》中的题目「把字符串转换成整数」是类似的,都是将字符串转换成整数。

具体的解释已经在代码中标注了,但有一点需要注意的是:在转换过程中使用的是负数进行运算,因为 int 范围的最小值 Integer.MIN_VALUE 在变成正数的时候会导致溢出,所以这里用负数进行计算。

getChars() 方法

该方法主要是将某个 int 类型的数值放到 char 类型的 buf 数组里面,如下所示:

 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
static void getChars(int i, int index, char[] buf) {
    int q, r;
    int charPos = index;
    char sign = 0;

    // 如果 i 小于 0,则用 sign 记下负号 '-',然后将 i 转换成正数
    if (i < 0) {
        sign = '-';
        i = -i;
    }

    // 处理高位的两个字节
    while (i >= 65536) {
        q = i / 100;
        // 下面的计算方式等同于 r = i - (q * 100);
        r = i - ((q << 6) + (q << 5) + (q << 2));
        i = q;
        buf [--charPos] = DigitOnes[r];
        buf [--charPos] = DigitTens[r];
    }

    //处理低位的两个字节
    // assert(i <= 65536, i);
    for (;;) {
        q = (i * 52429) >>> (16+3);
        // 下面的计算方式等同于 r = i-(q*10)
        r = i - ((q << 3) + (q << 1));  
        buf [--charPos] = digits [r];
        i = q;
        if (i == 0) break;
    }
    if (sign != 0) {
        buf [--charPos] = sign;
    }
}

该方法将 int 类型的数值分为 高位的两个字节低位的两个字节 分别进行处理。

int 占 4 个字节,1 字节 = 8 位,4 字节 = 32 位。

  • while (i >= 65536) 用于处理 int 高位的两个字节,也就是高 16 位。
  • 对于 for (;;) 内部,处理的是低 16 位,这里在计算 乘法 的时候使用的是 加法移位 操作,计算起来更加高效。同时 digits 数组用于获取对应的字符,这一点在之前已经提到过。

toString() 方法

这里有 3 个重载的 toString() 方法,如下所示:

 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
// 第一个 toString() 方法
public String toString() {
    return toString(value);
}

// 第二个 toString() 方法
public static String toString(int i) {
    if (i == Integer.MIN_VALUE)
        return "-2147483648";
    int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
    char[] buf = new char[size];
    getChars(i, size, buf);
    return new String(buf, true);
}

// 第三个 toString() 方法
public static String toString(int i, int radix) {
    if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX)
        radix = 10;

    /* Use the faster version */
    if (radix == 10) {
        return toString(i);
    }

    char buf[] = new char[33];
    boolean negative = (i < 0);
    int charPos = 32;

    if (!negative) {
        i = -i;
    }

    while (i <= -radix) {
        buf[charPos--] = digits[-(i % radix)];
        i = i / radix;
    }
    buf[charPos] = digits[-i];

    if (negative) {
        buf[--charPos] = '-';
    }

    return new String(buf, charPos, (33 - charPos));
}

对于第二个 toString() 方法,首先用 stringSize() 方法得到数值 i 的位数,然后利用 getChars() 方法获取数值 i 所对应的 char 数组,最后再 new 一个 String 返回。

对于第三个 toString() 方法,多了一个带有 进制 的参数。只要不在 2 到 36 进制之内的都会被处理成 十进制,这里首先将数值转换成 负数 进行处理,十进制转换成其它进制的方法就是不断的除以进制数,从而得到余数,然后将余数反过来,从而得到最后的结果。

valueOf() 方法

这里有三个 valueOf() 方法,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 第一个 valueOf() 方法
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

// 第二个 valueOf() 方法
public static Integer valueOf(String s) throws NumberFormatException {
    return Integer.valueOf(parseInt(s, 10));
}

// 第三个 valueOf() 方法
public static Integer valueOf(String s, int radix) throws NumberFormatException {
    return Integer.valueOf(parseInt(s,radix));
}

重点看第一个 valueOf() 方法,这里有个 IntegerCache 缓存,对于缓存范围内的 Integer 对象,直接从缓冲中取就可以了,否则就需要重新 new 一个 Integer 对象,即实例化一个 Integer 对象。

这里涉及到了 自动装箱/自动拆箱,对于 -128 <= i <= 127 范围内的数值,返回的是缓存中的对象,并没有重新创建一个新对象,例如 Integer a = 123;,这里就实现了 自动装箱,将 int 类型的 123 转换成对应的包装类 Integer,反编译源码可以看到,执行的就是调用了 `valueOf() 方法,即 Integer a = Integer.valueOf(123);

而对于范围以外的数,比如 128 来说,则会直接返回一个 Integer 对象。

自动拆箱 就是将 包装类型 转换成 基本数据类型,如下所示:

1
2
Integer a = new Integer(123);
int b = a;

这里的 int b = a; 通过反编译源码后可以看到,其实是调用了 intValue() 方法,即 int b = a.intValue();。将 Integer 类型转换成 int 类型。

equals() 方法

equals() 方法首先判断对象 obj 是不是 Integer 类型,如果是的话,再将 obj 强转为 Integer,通过 自动拆箱 再比较值是否相等。如下所示:

1
2
3
4
5
6
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

compare() 方法

这里直接 return 了一个三目运算,还是嵌套的三目运算。如果 x < y,则返回 -1;如果 x == y,则返回 0;如果 x > y,则返回 1

1
2
3
public static int compare(int x, int y) {
    return (x < y) ? -1 : ((x == y) ? 0 : 1);
}

除了以上的方法,其它的例如 reverse(int i)bitCount(int i)xxxValue() 等方法,在这里就不详细的展开了,你可以通过查看源码进行阅读。