第二章 Java的包装类之Integer源码分析

前言

第一章详细分析了java的类型转换,不同数据类型之间的强转和隐转。
本来第二章应该分析面向对象的基础知识的,但是发现这种只有理论的知识分析起来很枯燥,并且也不容易理解。所以从第二章开始,将逐渐在文中阐述自己对面向对象的一些认识和理解。如果有不对的地方还望能多多帮忙斧正。
这一章基于JDK1.8版本,分析JAVA的包装类之Integer,因为这个类是经常用到的类,基础中的基础,所以分析透彻是非常有必要的。

面向对象

JAVA是一个世界,虚拟的世界。现实生活中的世界,是真实的世界。不管是什么样的世界,其组成原理必然遵循一定的法则。在我的理解中,JAVA是一个高度模仿现实世界的代码世界。古人云:道可道,非常道。这个道就是现实世界中万物遵循的一个法则,万物的抽象。
在太极中,道生两仪,两仪生四象,四象生八卦。道就是万物的开始。二进制就是计算机的开始。Object就是Java的开始。
于是在这基础上,诞生了形形色色的物种和物质。现实世界中有各种人,各种动物,各种植物,各种建筑。如果把这些用JAVA来模仿的话,就是不同种类的类,有Integer类,有String类,有File类,有Thread类等等…类是面向对象的开始。
世界有了这些人和物以后,它们便开始协调的配合,创建了工厂(Factory),发电机,采矿机(Mybatis),城市(Spring),等等。世界慢慢开始运转起来了。就像网站一样,可以给用户使用了。这个网站便是你创造的世界。
类是一个对象的模具。可以用人来比喻。它有属性,有对外的接口,也有支撑接口的方法。
比如:
属性(细胞、血液、蛋白质等等)
对外的接口(手、眼、鼻、嘴等等)
支撑接口的方法(骨骼、心脏、肺、肾等等);不同的器官支撑不同的对接,或者共同支撑一个对外接口。它们是多对多关系。

Integer

今天分析的这个类——Integer,也从这三个部分来分析。由外层向内层逐渐解析。我大概做了一个图,将Integer这个类做了几个划分。
在这里插入图片描述
图中左侧是对外API及部分内部方法,有三四十个。左侧是其属性。
Api我们大概分析常用的几种,下面黄色基本很少很少用,也比较复杂,等来日学艺精进后再来研究,灰色的很简单基本不用。
所以本章重点分析最上面的四组方法,分别对应的API有

第一组:构造器、parseInt();
第二组:valueOf()、IntegerCatch内部类
第三组:toString()、getChars()、stringSize();
第四组:toHexString()、toOctalString()、toBinaryString()、toUnsignedString0、formatUnsignedInt()

第一组:构造器、parseInt()

Integer的构造器有两个,如下:

# 1. 以int 类型为入参
public Integer(int value) {
    this.value = value;
}

# 2. 以String 类型为入参
public Integer(String s) throws NumberFormatException {
    this.value = parseInt(s, 10);
}

其中,以int类型为入参很简单,就是将入参值赋予给Integer对象中维持的value属性。
而以String 为入参的构造器,其核心调用的是parseInt方法,因此,我们具体来分析一下parseInt();

(1) parseInt()方法

parseInt()方法的作用,就是将一个字符串转换为数字。
它有两个方法重载。分别是
parseInt(String s) 和 pasenInt(String s ,int radix),其中 s 是要转换的对象,radix是转换的进制基数,如radix = 10,就是十进制。parseInt(String s) 方法默认是转换为十进制。 我们重点分析 parseInt(String s,int radix) 。

parseInt(String s,int radix) 注释翻译

/** 
 * Parses the string argument as a signed integer in the radix
 * specified by the second argument. 
 * 
 * 翻译:将字符串参数作为基数中的有符号整数进行分析由第二个参数指定
 * 
 * The characters in the string
 * must all be digits of the specified radix
 *  (as determined by  whether {@link java.lang.Character#digit(char, int)}  returns a nonnegative value),
 * 
 * 翻译:字符串中的字符必须都是指定基数的数字,
 *      就像 Character类中的 digit(char,int)方法一样,返回一个非负值。
 * 
 *   except that the first character may be an
 * ASCII minus sign {@code '-'} ({@code '\u005Cu002D'}) to
 * indicate a negative value or an ASCII plus sign {@code '+'}
 * ({@code '\u005Cu002B'}) to indicate a positive value. The
 * resulting integer value is returned.
 * 
 * 翻译:第一个字符可能是ASCII减号(-)或者加号(+),都将返回一个整数值。
 * 
 * <p>An exception of type {@code NumberFormatException} is
 * thrown if any of the following situations occurs:
 * 
 * 翻译: 如果发生下列情况之一,将会返回NumberFormatException
 * 
 * <ul>
 * <li>The first argument is {@code null} or is a string of
 * length zero.
 * 
 * 翻译: 第一个参数值是null对象 或者其 string长度为0的情况
 * 
 * <li>The radix is either smaller than
 * {@link java.lang.Character#MIN_RADIX} or
 * larger than {@link java.lang.Character#MAX_RADIX}.
 * 
 * 翻译: radix参数值没有在 Character.MIN_RADIX 到 Character.MAX_RADIX 之间的
 * 
 * <li>Any character of the string is not a digit of the specified
 * radix, except that the first character may be a minus sign
 * {@code '-'} ({@code '\u005Cu002D'}) or plus sign
 * {@code '+'} ({@code '\u005Cu002B'}) provided that the
 * string is longer than length 1.
 * 
 * 翻译:转换对象的每个字符都是一个数字,加号或者减号除外。不能只有一个加号和减号
 * 
 * <li>The value represented by the string is not a value of type
 * {@code int}.
 * </ul>
 *  
 * 翻译:转换对象表达的不是一个int类型的数字。而是其他类型如 char 或者 boolean等等
 *
 * <p>这里举了几个例子:
 * <blockquote><pre>
 * parseInt("0", 10) returns 0
 * parseInt("473", 10) returns 473
 * parseInt("+42", 10) returns 42
 * parseInt("-0", 10) returns 0
 * parseInt("-FF", 16) returns -255
 * parseInt("1100110", 2) returns 102
 * parseInt("2147483647", 10) returns 2147483647
 * parseInt("-2147483648", 10) returns -2147483648
 * parseInt("2147483648", 10) throws a NumberFormatException
 * parseInt("99", 8) throws a NumberFormatException
 * parseInt("Kona", 10) throws a NumberFormatException
 * parseInt("Kona", 27) returns 411787
 * </pre></blockquote>
 *
 * @param      s   the {@code String} containing the integer
 *                  representation to be parsed
 * @param      radix   the radix to be used while parsing {@code s}.
 * @return     the integer represented by the string argument in the
 *             specified radix.
 * @exception  NumberFormatException if the {@code String}
 *             does not contain a parsable {@code int}.
 */

方法解析

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.
     * 警告: 此方法可以在初始化IntegerCache之前的VM初始化前调用。注意不要使用valueOf() 方法
     */
	
	// 1.  入参判断
    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");
    }
	// 2. 实现转换
    int result = 0;  //初始化返回参数
    boolean negative = false;   //默认这个字符串是一个正数,而不是负数,如“-234”
    int i = 0, len = s.length();   //  i 表示的真值开始的下标
    int limit = -Integer.MAX_VALUE;
    int multmin;
    int digit;
    
	// (1)  判断字符串的正负,及真值的长度
    if (len > 0) {
        char firstChar = s.charAt(0);
        //这里通过比较第一个字符ASCII码的值,来判断
        if (firstChar < '0') { // Possible leading "+" or "-"
            // 如果为负号,转换标志参数,并界定下限不得低于 Integer能表达的最小值。
            if (firstChar == '-') {
                negative = true;
                limit = Integer.MIN_VALUE;
            } else if (firstChar != '+')
             // 如果不为正或负,证明格式有误,抛出异常
                throw NumberFormatException.forInputString(s);
                
			// 当第一个字符小于0的情况下,肯定有一个符号(无论正负),但不能只有一个符号,因此需要判断字符串的长度
            if (len == 1) // Cannot have lone "+" or "-"
                throw NumberFormatException.forInputString(s);
            
            i++; // i 表示的真值开始的下标,此时字符串第一个字符为符号位,因此真值是从第二个开始,
                // 也就是下标要加1。 如果字符串前面没有符号,则i从0开始就表示真值(此情况发生在正数身上)
        }

//判断到这步,可以确定第一个字符肯定为+ 或者 为 -了,
//但是无法判断后续的字符是否是数字还是其他字符。如 “++23++2”。

	  //  (2) 对数值按radix表示的进制基数进行转换。
        multmin = limit / radix;
        while (i < len) {
            // Accumulating negatively avoids surprises near MAX_VALUE
            //  翻译:负数累加避免接近最大值时出现意外
            
			// 这里调用digit(char,int)方法,digit()是个边界值判断,不过边界返回字符数字本身数值,超过边界即返回 -1
			// 这个方法就是判断每个字符是不是数字,并返回相应的值
            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 {
		// 就像开头注释所说,如果这个字符串的长度为0,将会抛出异常。如字符串 "".
        throw NumberFormatException.forInputString(s);
    }
    // 根据正负形判断返回正负值
    return negative ? result : -result;
}

(2) 构造器

parseInt() 方法分析完后,现在可以很清晰的理解构造器了。
构造器的两个方法

# 1. 以int 类型为入参
public Integer(int value) {
    this.value = value;
}

# 2. 以String 类型为入参
public Integer(String s) throws NumberFormatException {
    this.value = parseInt(s, 10);
}

第二组 valueOf() 和 IntegerCatch内部类

(1)valueOf()

valueOf 方法的作用就是包装类的自动装箱。
它有两个重载方法:

// int类型入参,自动装箱过程
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
} 
// String类型过程,其调用了parseInt() 方法,先转为int类型,再装箱。
public static Integer valueOf(String s) throws NumberFormatException {
    return Integer.valueOf(parseInt(s, 10));
}

从valueOf的方法中可以看出,其内部调用了IntegerCatch缓存内存类。如果没有在缓存内部类最高限和最低限之内,就新创建一个对象,否则提供缓存类中的对象。缓存类是怎样结构的呢,下面来分析一下。

(2) IntegerCatch 内部类

private static class IntegerCache {
    static final int low = -128;    //限定了最低值为 -128
    static final int high;			//最高值未限定,在其初始化过程中有其他作用。	
    static final Integer cache[];	// 维持的Integer数组

	// 内部类加载过程中,初始化Integer数组,
    static {
        // high value may be configured by property
        // 翻译: 最高限定值可以通过配置来决定。
        
        int h = 127;  // 如果没有配置,则默认最高限值为127,否则可以通过配置java.lang.Integer.IntegerCache.high的值来决定
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);  //如果配置的属性值小于127,那么还是采用默认的127作为最高限值
                
                // Maximum array size is Integer.MAX_VALUE
                // 如果配置的值过大,这里限定了最大不得超过Integer能表示的最大值 减去 129
                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;

	// 最后将最大缓存到最小缓存之间的对象存到Integer数组中
        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++); 	//缓存中的顺序为 从最低值-128开始到最高值。
           

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

    private IntegerCache() {}
}

因此,回到valueOf() 方法中,如果调用者传入的值在最大缓存和最小缓存之间,那么就将IntegerCatch中对应的对象返回给调用者。否则重新创建一个对象。

第三组:toString() 、 getChars() 、StringSize()

为什么将这三个方法划分为一组呢,因为toString方法有两个重载,先看第一个toString方法
作用是将入参数字按不同的进制进行转换

(1) toString() 方法

这个方法中调用了 difits[] 数组。数组中记录了0-9,a-z的小写字符。共36个字符。
为什么要定义这个数组呢? 因为 Character.MIN_RADIX =2,Character.MAX_RADIX = 36.
代表一个入参数值转换进制,从2进制到36进制都是允许的。所以需要提供这么多可能的字符,不同进制取不同的值。

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'
};

 public static String toString(int i, int radix) {
 
	//如果超过了进制范围,默认为10进制
    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]; // 创建一个33位长度的空数组
    boolean negative = (i < 0);
    int charPos = 32; //下标从32开始,代表buf[] 的最后一个值开始。

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

    while (i <= -radix) {
        buf[charPos--] = digits[-(i % radix)]; // 这里根据转换进制的不同,获取数组中对应的字符。
        										// 对于超过10进制而言的数据,允许小写字母来表示
        										// 最常见的16进制:0x834等等。
        i = i / radix;
    }
    buf[charPos] = digits[-i];

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

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

(1)toString()

第二个toString()方法,其内部调用了getChars() 和 StringSize();

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);
}

(2) getChars()方法

这个方法的目的是为了获取每个数字代表的字符是什么。看了这个方法,才知道大神们为了提升效率,无所不用其极!!巧妙了利用了两个一维数组,精妙的设计,每次迭代可以识别出相邻两位数字,惊叹!。
先看下定义的两个二维数组。

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',
    } ;

方法体:
	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));  // 这里使用位运算,向左移了6位也就是乘以 2^6 = 64,  2^5 =32, 2^2 = 4.
        										   // 整个意思就是 q * 100
        										   // 可能为了提升CPU做乘法运算的效率,因此这样写的。
        										   // 每次循环,r 都为 入参值的最后两位。比如36
        i = q;
        buf [--charPos] = DigitOnes[r];  // 然后 36在这里是第三行,得到3,
        buf [--charPos] = DigitTens[r];  // 然后 36在这里是第三行, 得到6,
    }

    // 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;
    }
}

(3) stringSize() 方法

这也是一个很巧妙的设计,值得学习。通过比较该位上最大值来判断其长度,并将长度融入下标中。极大化的精简了代码和程序的运行。

 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;

第四组:toHexString()、toOctalString()、toBinaryString()、toUnsignedString0、formatUnsignedInt()

上个结构图:
在这里插入图片描述
这一组方法中,对外接口分别是 toHexString()、toOctalString()、toBinaryString()。这几个接口的功能就是将数字转换为16进制、8进制、2进制的作用。
实例演示

int a  = 16;
  System.out.println(Integer.toBinaryString(a));  // 结果:10000
  System.out.println(Integer.toOctalString(a));  // 结果:20
  System.out.println(Integer.toHexString(a));  	// 结果 : 10

这三个对外接口的核心实现都是 toUnsignedString0()方法。源码是这样的。

 //  toBinaryString()方法: 
 public static String toBinaryString(int i) {
    return toUnsignedString0(i, 1);
}
//   toOctalString()方法
public static String toOctalString(int i) {
    return toUnsignedString0(i, 3);
}
//   toHexString()方法
public static String toHexString(int i) {
    return toUnsignedString0(i, 4);
}

下来具体分析核心方法 toUnsignedString0();

(1) toUnsignedString0()

这个方法的作用就是

 /**
 * Convert the integer to an unsigned number.
 */
private static String toUnsignedString0(int val, int shift) {
    // assert shift > 0 && shift <=5 : "Illegal shift value";
    
    //这里用了Integer.numberOfLeadingZeros(val)方法,这个方法作用是计算传入值转换为二进制后,
    // 真值前面有多少个零. 如: System.out.println(Integer.numberOfLeadingZeros(16));,
    // 16的二进制是10000.Integer占4个字节共计32位,因此前面有27个零。这个结果就是27;
    int mag = Integer.SIZE - Integer.numberOfLeadingZeros(val);
    
    // 这一步暂时没看懂
    int chars = Math.max(((mag + (shift - 1)) / shift), 1);
    char[] buf = new char[chars];

    formatUnsignedInt(val, shift, buf, 0, chars);

    // Use special constructor which takes over "buf".
    return new String(buf, true);
}

总结

第一组作用是介绍将字符串转换为数字。
第二组方法是介绍Integer的自动装箱。
第三组方法是介绍将数字转换为不同进制的字符串表示,返回值是字符串。
第四组方法也是介绍将数字转换为不同进制的字符串表示,返回值是字符串。和第三组方法有点重复了。

以上大概就是Integer类常用的方法源码分析。这一部分只占Integer整个类的一小部分,需要学习的还有很多很多啊。不过那些不常见不常用的,等以后用到了再来学习吧!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值