当你掌握Java语言到了一定的阶段,或者说已经对Java的常用类和API都使用的行云流水。你会不会有一些思考?比如,这个类是如何设计的?这个方法是怎么实现的?接下来的一系列文章,我们一起学习下Java的一些常见类的源码。本篇,一起分析下Integer的源码。
目录
一、两道Integer的题目
二、Integer类图
三、String转int
1、Integer.parseInt
2、Integer.valueOf
四、总结
可能有些同学java水平比较高,或者认为自己没必要去看Integer源码。那你不妨看下接下来这几道题目,看看你是否都能答对。
1、如下代码输出什么?
public class IntegerTest1 {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1 == i2);
System.out.println(i3 == i4);
}
}
答案是:true false
2、如下代码输出什么?
public class IntegerTest {
public static void main(String[] args) {
String s = "10";
System.out.println(Integer.getInteger(s));
}
}
答案是:null(咦,为什么不是10?)
如果这两道题目都答对了,那你可以叉掉这篇文章,因为我也不想浪费你的时间。如果你答错了,那么不妨一起学习下Integer的源码?
那么,接下来,我们一起看下Integer的源码吧。Integer源码不算短,1800+行代码。其中有很多api是不常用的,因此,我们也仅去挑一些常用的api去看下其实现。如下是Integer及其关联类/接口的类图:
通过Integer类的类图,我们总结下它的特点:
- Integer类继承自抽象类Number
- Integer类实现了Comparable接口
- Integer类使用final修饰,因此不可以有子类(不能被继承)
在日常工作中,我们经常会将一个代表数字的String,转为int,那么Java给我们提供了两个方法:
public static int parseInt(String s) throws NumberFormatException {
return parseInt(s,10);
}
在使用这个方法的时候,我们需要注意trycatch一下NumberFormatException,否则当输入的String不是数字或者超过integer的范围时会产生异常。
另外,该方法默认是10进制,当然我们也可以调用两个参数的方法传入进制去转为其他进制的int,但这不常用:
public static int parseInt(String s, int radix)
throws NumberFormatException
{...}
接下来,详细看下该方法的实现:
首先,会判断传入的String不为null,检验传入的进制参数在范围内[2 , 36],而且会判断字符串的长度大于0,否则会抛出NumberFormatException:
if (s == null) {
throw new NumberFormatException("null");
}
if (radix < Character.MIN_RADIX) {
throw new NumberFormatException("radix " + radix +
" less than Character.MIN_RADIX");
}
if (radix > Character.MAX_RADIX) {
throw new NumberFormatException("radix " + radix +
" greater than Character.MAX_RADIX");
}
if (len > 0) {
...
} else {
throw NumberFormatException.forInputString(s);
}
当字符串满足转化为int的条件时,就执行将String转为int的代码(上述被...省略的代码)。我们也分两步来看:
首先,根据字符串的首字符判断是正数、负数或是非法。
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++;
}
(1)如果第一个字符是'-',会把表示正负数的negative置为true,同时把其limit置为Integer的最小值;
(2)如果不是'-'且如果不是'+',那说明是其他的字符,抛出异常;
(3)如果首字符是'+'或'-',但是长度为1,说明是"+"或"-",也是非数字,抛出异常。
(4)如果首字符合法,那么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;
(1)遍历字符串的字符,调用digit函数,该函数在不能转为数字时返回-1
(2)如果有字符不是数字(digit<0),那么抛出异常。
(3)如果没问题,那就一步步计算转为int,会判断是否超过范围,超过范围则抛出异常
(4)最后,如果是负数返回result,如果是正数,返回-result
在这里,可能有的同学没看懂,为什么负数返回的是result,而正数返回的是-result。我们把字符串数字转为十进制的方法,比如把"1234"转为十进制,其实是这么来的:
((((1 ✖️10)+ 2)✖️10) + 3)✖️10 + 4
= ((12 ✖️10) + 3) ✖️10 + 4
= (120 + 3)✖️10 + 4
= 123✖️10 + 4
= 1230 + 4
= 1234
而方法里面其实是反过来实现的:
((((-1 ✖️10)- 2)✖️10) - 3)✖️10 - 4
= ((-12 ✖️10) - 3) ✖️10 - 4
= (-120 - 3)✖️10 - 4
= -123✖️10 - 4
= -1230 - 4
= -1234
所以,它用的不是正向累加法,而是负向累加法。其实代码里面也有注释(负向累加避免在最大值附近发生意外):
// Accumulating negatively avoids surprises near MAX_VALUE
第二种方法就是Integer.valueOf,该方法最后还是调用的parseInt。注意其返回值是Integer,而不是int。不过,现在已经不用unboxing了,可以直接使用int去接收返回值。
public static Integer valueOf(String s, int radix) throws NumberFormatException {
return Integer.valueOf(parseInt(s,radix));
}
接下来,看看valueOf的实现:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
suprise来了,Integer.valueOf( int i)这个方法,出现了IntegerCache这个内部类,而且会返回cache里的对象或者一个新的Integer对象。那么,IntegerCache是什么?
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer[] cache;
static Integer[] archivedCache;
private IntegerCache() {
}
static {
int h = 127;
String integerCacheHighPropValue = VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
int size;
if (integerCacheHighPropValue != null) {
try {
size = Integer.parseInt(integerCacheHighPropValue);
size = Math.max(size, 127);
h = Math.min(size, 2147483518);
} catch (NumberFormatException var6) {
}
}
high = h;
VM.initializeFromArchive(Integer.IntegerCache.class);
size = high - -128 + 1;
if (archivedCache == null || size > archivedCache.length) {
Integer[] c = new Integer[size];
int j = -128;
for(int k = 0; k < c.length; ++k) {
c[k] = new Integer(j++);
}
archivedCache = c;
}
cache = archivedCache;
assert high >= 127;
}
}
可以看出,Integer内部维护了一个IntegerCache,范围是[-128,127]。valueOf方法,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
这其实解答了我们的第一个问题。 在这里顺便也看下我们的第二个问题,为什么Integer.getInteger("10")返回的是null,而不是10。其实调用的是System.getProperty,跟String转int半毛钱关系没有:
public static Integer getInteger(String nm, Integer val) {
String v = null;
try {
v = System.getProperty(nm);
} catch (IllegalArgumentException | NullPointerException e) {
}
if (v != null) {
try {
return Integer.decode(v);
} catch (NumberFormatException e) {
}
}
return val;
}
其实Integer我们常用的无非就是这两个String转int的方法,Integer还提供了一些简单的计算方法例如max,min,sum,其实我们也用不到。还有intValue方法,从java1.6开始,我们也不需要拆箱(unboxing)了,所以也用不到。
本篇基于Integer的两个常用方法,看了其关键代码的实现,也知道了其内部维护着一个缓存。通过分析其源码实现,也解答了开篇抛出的两个问题。