上次我们已经对String
类源码做了一个简单的总结,感兴趣的小伙伴可以去看一下啃碎JDK源码(一):String,今天来看看Java的Integer
包装类。
先来看看Integer
实现了哪些接口
public final class Integer extends Number implements Comparable<Integer> {
可以看到Integer
用final
修饰,代表不可被继承,继承Number
类实现Comparable
接口。
private final int value; public static final int MIN_VALUE = 0x80000000;//最大值 public static final int MAX_VALUE = 0x7fffffff;//最小值 public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
可以看出使用了int
类型的value
来存储值,并且定义了Integer
的最大值为2^31-1
,最小值为-2^31
。Integer
的基本数据类型为int
。
先来看一下Integer
的两个构造函数:
public Integer(int value) { this.value = value; } public Integer(String s) throws NumberFormatException { this.value = parseInt(s, 10); }
那么这个parseInt
方法是干啥用的呢?
方法parseInt(String s,int radix)
的目的是输出一个十进制数。
比如:parseInt(1010,2)
意思就是:输出2进制数1010在十进制下的数。
我们平时用到Integer.parseInt("123");
其实默认是调用了int i =Integer.parseInt("123",10);
下面是源码:
public static int parseInt(String s, int radix) throws NumberFormatException { /* * WARNING: This method may be invoked early during VM initialization * before IntegerCache is initialized. Care must be taken to not use * the valueOf method. */ if (s == null) { throw new NumberFormatException("null"); } //判断基数是否在 2~36之间 if (radix < Character.MIN_RADIX) { throw new NumberFormatException("radix " + radix + " less than Character.MIN_RADIX"); } if (radix > Character.MAX_RADIX) {//36 throw new NumberFormatException("radix " + radix + " greater than Character.MAX_RADIX"); } int result = 0; boolean negative = false;//是否为负数,默认为false int i = 0, len = s.length(); int limit = -Integer.MAX_VALUE; int multmin; int digit; if (len > 0) { char firstChar = s.charAt(0); if (firstChar < '0') { // Possible leading "+" or "-" if (firstChar == '-') {//如果是负数,negative赋值为true,限制变为int的最小值 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++; } /**multmin防止数据溢出 *如果是正数就是-2,147,483,64 *如果是负数就是-2,147,483,64 **/ multmin = limit / radix; while (i < len) { // Accumulating negatively avoids surprises near MAX_VALUE //获取字符转换成对应进制的整数 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; }
接下来来看一个比较经典的问题,看下面代码:
Integer a = 100; Integer b = 100; Integer c = 200; Integer d = 200; System.out.println(a == b); System.out.println(c == d);
你认为这里是四个不同的对象,那么输出应该都是flase?
不,输出结果为:
true
false
通过javap -c/javap -verbose
命令可以查看字节码;红色圈圈里就是我们jdk5之后的基本类型的自动包装的字节码实现,可以看出,此处是调用了Integer.valueOf(..)
方法的:说白了就是Integer a = 100
等价于Integer a = Integer.valueOf(100)
JDK1.5之后,java提供了自动装箱和自动拆箱的功能。自动装箱也就是调用了Integer类的一个静态方法valueOf
方法,那我们来看看源码是如何实现的:
看看IntegerCache
是个什么东西:
可以看到IntegerCache
是一个静态内部类,low的值已经写死-128,而high的值由你的虚拟机决定sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high")
,既然是一个参数也就意味着你可以动态设置。然后在循环中将low - high
之间数字的装箱后方法cache[]
这个Integer类型的数组中。这样就完成了缓存。
因为当我们调用valueOf
方法时传入-128到127之间的数字时,Integer
会给我们返回同样的对象,记住这一点以后面试的时候也可以和面试官吹吹牛逼了!
接下来我们来看看Integer
实现的equals
、hashcode
和toString
等方法:
public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false; }
可以看到它会先判断类型是否符合,然后进行拆箱比较操作。
看下hashcode
方法:
@Override public int hashCode() { return Integer.hashCode(value); } public static int hashCode(int value) { return value; }
可以看到hashcode
直接返回本身的int值。
Integer
的toString
方法 是我认为一个比较有趣的地方:
看下toString(int i)
源码:
在上面用到了stringSize
方法,就是求这个Integer
数的长度,我们来看看他是如何实现的:
可以看到这段代码在计算Integer
数长度时,构建了一个一维数组,然后拿x与数组每个值进行比较。
还有一个getChars
方法是获取数值对应的字符串,其中有两个地方使用了非常巧妙的方式来进行除法运算和取余运算。在计算机中,a/b
和 a%b
相比较位运算,都是比较费时的计算的。下面来看看jdk中是如何优化计算的:
static void getChars(int i, int index, char[] buf) { int q, r; int charPos = index; char sign = 0; if (i < 0) { sign = '-'; i = -i; } // Generate two digits per iteration while (i >= 65536) { q = i / 100; // really: r = i - (q * 100); r = i - ((q << 6) + (q << 5) + (q << 2)); i = q; buf [--charPos] = DigitOnes[r]; buf [--charPos] = DigitTens[r]; } // Fall thru to fast mode for smaller numbers // assert(i <= 65536, i); for (;;) { q = (i * 52429) >>> (16+3); r = i - ((q << 3) + (q << 1)); // r = i-(q*10) ... buf [--charPos] = digits [r]; i = q; if (i == 0) break; } if (sign != 0) { buf [--charPos] = sign; } }
其中有一行代码:
q = (i * 52429) >>> (16+3);
上面这行公式约等于 q * 0.1
,也就是说使用乘法计算比使用除法高效。
思路是这样:
当 i >= 65536时,是每两位取出数字,i /= 100,例如 i = 567235474,
(1)先取最后两位 7 和 4 放入buf数组中,i = 5672354,buf = { , , , , , , , '7', '4'};
(2)再取最后两位 5 和 4 放入buf数组中,i = 56723,buf = { , , , , , '5', '4', '7', '4'};
当 i < 65536 时,跳出循环,采用每一次取出一位数字,也就是 i /= 10
(3)取最后一位 3 放入buf数组中,i = 5672,buf = { , , , , '3', '5', '4', '7', '4'};
(4)取最后一位 2 放入buf数组中,i = 567,buf = { , , , '2', '3', '5', '4', '7', '4'};
(5)取最后一位 7 放入buf数组中,i = 56,buf = { , , '7', '2', '3', '5', '4', '7', '4'};
(6)取最后一位 6 放入buf数组中,i = 5,buf = { , '6', '7', '2', '3', '5', '4', '7', '4'};
(7)取最后一位 5 放入buf数组中,i = 0,buf = { '5', '6', '7', '2', '3', '5', '4', '7', '4'},结束。
关于Integer
类暂时介绍到这里,有关其它的包装类Long
等源码部分也是类似的,后续就不再介绍,有兴趣的小伙伴可以去研究一下。