Java源码分析(一)Integer

    当你掌握Java语言到了一定的阶段,或者说已经对Java的常用类和API都使用的行云流水。你会不会有一些思考?比如,这个类是如何设计的?这个方法是怎么实现的?接下来的一系列文章,我们一起学习下Java的一些常见类的源码。本篇,一起分析下Integer的源码。

目录

一、两道Integer的题目

二、Integer类图

三、String转int    

1、Integer.parseInt

2、Integer.valueOf

四、总结 


一、两道Integer的题目

    可能有些同学java水平比较高,或者认为自己没必要去看Integer源码。那你不妨看下接下来这几道题目,看看你是否都能答对。   

1、如下代码输出什么?

扫描二维码关注公众号,回复: 16486091 查看本文章
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的源码吧。Integer源码不算短,1800+行代码。其中有很多api是不常用的,因此,我们也仅去挑一些常用的api去看下其实现。如下是Integer及其关联类/接口的类图:

    通过Integer类的类图,我们总结下它的特点:

  • Integer类继承自抽象类Number
  • Integer类实现了Comparable接口
  • Integer类使用final修饰,因此不可以有子类(不能被继承)

三、String转int    

    在日常工作中,我们经常会将一个代表数字的String,转为int,那么Java给我们提供了两个方法:

1、Integer.parseInt

    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

2、Integer.valueOf

    第二种方法就是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的两个常用方法,看了其关键代码的实现,也知道了其内部维护着一个缓存。通过分析其源码实现,也解答了开篇抛出的两个问题。

猜你喜欢

转载自blog.csdn.net/qq_21154101/article/details/132380640