Java源码分析(一)Integer

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

目录

一、两道Integer的题目

二、Integer类图

三、String转int

1、Integer.parseInt

2、Integer.valueOf

四、总结


一、两道Integer的题目

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

1、如下代码输出什么?

java 复制代码
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、如下代码输出什么?

java 复制代码
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

java 复制代码
    public static int parseInt(String s) throws NumberFormatException {
        return parseInt(s,10);
    }

在使用这个方法的时候,我们需要注意trycatch一下NumberFormatException,否则当输入的String不是数字或者超过integer的范围时会产生异常。

另外,该方法默认是10进制,当然我们也可以调用两个参数的方法传入进制去转为其他进制的int,但这不常用:

java 复制代码
public static int parseInt(String s, int radix)
                throws NumberFormatException
    {...}

接下来,详细看下该方法的实现:

首先,会判断传入的String不为null,检验传入的进制参数在范围内[2 , 36],而且会判断字符串的长度大于0,否则会抛出NumberFormatException:

java 复制代码
        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的代码(上述被...省略的代码)。我们也分两步来看:

首先,根据字符串的首字符判断是正数、负数或是非法。

java 复制代码
            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++,接下来看非符号的字符。

当然,如果首字符不是符号,而是数字,那么就直接走接下来的代码:

java 复制代码
            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

所以,它用的不是正向累加法,而是负向累加法。其实代码里面也有注释(负向累加避免在最大值附近发生意外):

java 复制代码
// Accumulating negatively avoids surprises near MAX_VALUE

2、Integer.valueOf

第二种方法就是Integer.valueOf,该方法最后还是调用的parseInt。注意其返回值是Integer,而不是int。不过,现在已经不用unboxing了,可以直接使用int去接收返回值。

java 复制代码
    public static Integer valueOf(String s, int radix) throws NumberFormatException {
        return Integer.valueOf(parseInt(s,radix));
    }

接下来,看看valueOf的实现:

java 复制代码
    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是什么?

java 复制代码
    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半毛钱关系没有:

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

相关推荐
AskHarries2 分钟前
Java字节码增强库ByteBuddy
java·后端
一颗松鼠7 分钟前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
有梦想的咸鱼_9 分钟前
go实现并发安全hashtable 拉链法
开发语言·golang·哈希算法
海阔天空_201314 分钟前
Python pyautogui库:自动化操作的强大工具
运维·开发语言·python·青少年编程·自动化
天下皆白_唯我独黑21 分钟前
php 使用qrcode制作二维码图片
开发语言·php
小灰灰__22 分钟前
IDEA加载通义灵码插件及使用指南
java·ide·intellij-idea
夜雨翦春韭25 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
小远yyds27 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
何曾参静谧39 分钟前
「C/C++」C/C++ 之 变量作用域详解
c语言·开发语言·c++
程序媛小果1 小时前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot