Integer包装类源码阅读

背景
很多面试题都有Java拆装箱问题,其中Integer与int区别最多,下面结合自己看的一些视频及文章做下总结。

先上测试代码:

public static void main(String[] args) {  
    int i = 150;  
    Integer i2 = 150;  ❸
    Integer i3 = new Integer(150);  ❷

    System.out.println(i == i2); // ★ Integer会自动拆箱为int,所以为true  ❶
    System.out.println(i == i3); //true,理由同上    ❶
    System.out.println(i2 == i3);  //false 
    System.out.println(i2.equals(i3)); //true

    Integer i4 = 127;  ❸
    Integer i5 = 127;  ❸
    System.out.println(i4 == i5);//true  ❹
    Integer i6 = 128;  
    Integer i7 = 128;  
    System.out.println(i6 == i7);//false ❺
            
    Integer i8 = new Integer(127);  
    System.out.println(i5 == i8); //false  ❻
}  

❶属于自动拆箱,这里之所以选150测试,是避免Integer在-128到127之间进行缓存的误解。

注:自动装箱和拆箱是由编译器来完成的,编译器会在编译期根据语法决定是否进行装箱和拆箱动作。

❷属于直接创建Integer对象,new过程会直接从堆中新开辟一个内存保存Integer数据,将地址存入栈中对象。==比较的就是栈中对象的地址信息。

equals则不同,看源码

/**
 * 将此对象与指定对象进行比较。
 * 当且仅当参数不是null并且是一个包含与该对象相同的int值的整数对象时,结果是真的。
 */
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue(); // ⚘
    }
    return false;
}

public int intValue() {  // ♆
    return value;
}

public Integer(int value) {
    this.value = value;
}

private final int value;

可以看到,equals比较的是初始化时传入的int值。一般比较Integer值相等最好用equals()方法。

❸部分在编译时被翻译成:Integer i4 = Integer.valueOf(参数);

看源码:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

该部分代码进行了一个判断:在 IntegerCache.low 和 IntegerCache.high之间的值,则从IntegerCache.cache[i + (-IntegerCache.low)]数组对应的位置去数据,否则在堆内存中重新创建新的Integer对象。

为什么是 IntegerCache.cache[i + (-IntegerCache.low)],IntegerCache.low 和 IntegerCache.high又分别是多少?

最关键的部分来了,IntegerCache这个类,Ctrl + 左键 进行跟踪:

private static class IntegerCache {
    static final int low = -128;   // 缓存下界,值不可变
    static final int high;         // 缓存上界  
    static final Integer cache[];  //  cache缓存是一个存放Integer类型的数组

    static {
        // high值可以由属性进行配置
        int h = 127;   

        // getSavedProperty() 获取虚拟机启动时配置参数java.lang.Integer.IntegerCache.high的值,该值为自行设置的缓存上界,看下面代码
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) { // 判断调用者是否设置改参数
            try {
                // 将配置的integerCacheHighPropValue值传给i,parseInt()将字符串参数作为有符号的十进制整数进行解析。
                int i = parseInt(integerCacheHighPropValue);  // ⚘
                // 取配置的integerCacheHighPropValue值与127中最大的赋值给i,保证了上线至少是127
                i = Math.max(i, 127);
                // h取 i 与 Integer.MAX_VALUE - (-low) -1的最小值,其中 Integer.MAX_VALUE = 2^31 -1 = 2147483647,16进制标识为0x7fffffff
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // 如果属性不能解析为int,则忽略它,为parseInt()抛出的NumberFormatException异常 ,try -catch了,因此程序依然往下走.
            }
        }

        high = h;  // 没有直接使用high值进行操作,而是先初始h值再赋值给high,没想通设计者的这样写的具体目的

        // 创建一个长度为(high - low) + 1的Integer数组,此处为什么写成(high - low) + 1?为什么不是high - low,下面的循环写成k <= cache.length?
        cache = new Integer[(high - low) + 1]; 
        int j = low;   // 此处则控制了,cache缓存数组的下界是不可变的,new Integer(j++)。

        // 原来上面的写法,不仅直观,而且数组下标从0开始,而数组长度从1开始,如果写成high - low,k <= cache.length则会出现下标越界。
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

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

    private IntegerCache() {}
}   

IntegerCache是Integer的静态内部类,因此在Integer初始化时同时初始化IntegerCache及其所有属性。具体详情,看上面代码注释,便于查看。

关于 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high")
参数的设置

规范就是规范,设置java.lang.Integer.IntegerCache.high的值用于什么地方,对于该写法一目了然。而该值怎么设置?两种办法

-XX:AutoBoxCacheMax=128 或者 -Djava.lang.Integer.IntegerCache.high=128 进行设置

添加此设置后:第❺部分则为true

Eclipse可以通过 右击执行类 ——> Run AS ——> Run configurations ——> Arguments选项卡 ——> VM arguments中添加。

包装类 是否缓存
Boolean 全部缓存
Byte 全部缓存
Character <= 127 缓存
Short -128 — 127 缓存
Long -128 — 127 缓存
Float 没有缓存
Doulbe 没有缓存

再看 parseInt(String s)源码部分

/**
 * 将字符串参数作为有符号的十进制整数进行解析。除了第一个字符可以是用来表示负值的 ASCII 减号 '-' ( '\u002D') 
 * 外,字符串中的字符都必须是十进制数字。返回得到的整数值,就好像将该参数和基数 10 作为参数赋予 
 * parseInt(java.lang.String, int) 方法一样。
 */
public static int parseInt(String s) throws NumberFormatException { // ♆
    return parseInt(s,10);  // ⚘
}

内部使用 parseInt(String s,int radix)方法

/**
 * 使用第二个参数指定的基数,将字符串参数解析为有符号的整数。除了第一个字符可以是用来
 * 表示负值的 ASCII 减号 '-' ( '\u002D’)外,字符串中的字符必须都是指定基数的数字
 *(通过 Character.digit(char, int) 是否返回一个负值确定)。返回得到的整数值。
 * 
 * 如果发生以下任意一种情况,则抛出一个 NumberFormatException 类型的异常:
 * 
 * ● 第一个参数为 null 或一个长度为零的字符串。
 * ● 基数小于 Character.MIN_RADIX 或者大于 Character.MAX_RADIX。
 * ● 假如字符串的长度超过 1,那么除了第一个字符可以是减号 '-' ('u002D’) 外,
 * \字符串中存在任意不是由指定基数的数字表示的字符。
 * ● 字符串表示的值不是 int 类型的值。
 * 
 * 示例:parseInt("11", 10) 返回 11
 *      parseInt("11", 22) 返回 23
 *
 * @param s - 包含要解析的整数表示形式的 String
 * @param radix - 解析 s 时使用的基数,即以几进制输出
 * @return 使用指定基数的字符串参数表示的整数
 *
 */
public static int parseInt(String s, int radix) throws NumberFormatException { // ♆
    /*
     * 警告:因为valueOf方法使用了parseInt方法和IntegerCache对象,因此valueOf在parseInt初始化之前使用会出现异常
     */

     // 下面三个if用来判断参数是否合法。radix大小在2~36之间。
    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");
    }

    int result = 0;             // 解析结果
    boolean negative = false;   //是否是负数
    int i = 0, len = s.length();  //索引变量和字符串长度
    int limit = -Integer.MAX_VALUE;  // 最大值限制,加'-'号是为了后面的result < 做统一处理
    int multmin;    //基数下的最小值
    int digit;      //记录每一位的数字

    if (len > 0) {
        char firstChar = s.charAt(0);
        if (firstChar < '0') { // 所有的特殊符号和字母字符均小于'0',判断是否有'-'或'+'
            if (firstChar == '-') {  // 判断是否为'-'或'+'
                negative = true;     // 标识所传为负数
                limit = Integer.MIN_VALUE;
            } else if (firstChar != '+')  // 格式非法,既不为'-',也不为'+'则抛出异常
                throw NumberFormatException.forInputString(s);

            if (len == 1) // 排除只为'-'或者'+'的字符串情况
                throw NumberFormatException.forInputString(s);
            i++;
        }
        multmin = limit / radix; 
        while (i < len) {
            // 利用了Character类中的digit方法,作用是解析一个字符返回int值,基本就是'0'-'9'则返回 0-9
            digit = Character.digit(s.charAt(i++),radix); // ⚘
            if (digit < 0) {
                throw NumberFormatException.forInputString(s);
            }
            if (result < multmin) {
                throw NumberFormatException.forInputString(s);
            }
            // result进行基数转换,放在result -= digit前面,如转换"123",则为-(((1*22)+2)*22+3)即 1*22^2 +2*22^1+3*22^0
            result *= radix;  
            if (result < limit + digit) {
                throw NumberFormatException.forInputString(s);
            }
            result -= digit;   // 将字符处理后返回的digit赋值给result
        }
    } else {
        throw NumberFormatException.forInputString(s);
    }
    return negative ? result : -result;   // 这里进行上面加'-'统一处理的问题
}

再看 Character.digit(char ch, int radix) 源码

public static int digit(char ch, int radix) {  // ♆
    return digit((int)ch, radix); // ⚘
}

内部调用Character.digit(int codePoint, int radix)源码

/**
 * 返回使用指定基数的字符 ch 的数值
 *
 * 如果基数不在 MIN_RADIX <= radix <= MAX_RADIX 范围之内,
 * 或者 ch 的值是一个使用指定基数的无效数字,则返回 -1。
 * 如果以下条件中至少有一个为真,则字符是一个有效数字:
 * 
 * ● 方法 isDigit 为 true,且字符(或分解的单字符)的 Unicode 十进制数值小于指定的基数。在这种情况下,返回十进制数值
 * ● 字符为 'A' 到 'Z' 范围内的大写拉丁字母之一,且它的代码小于 radix + 'A' - 10。在这种情况下,返回 ch - 'A' + 10
 * ● 字符为 'a' 到 'z' 范围内的小写拉丁字母之一,且它的代码小于 radix + 'a' - 10。在这种情况下,返回 ch - 'a' + 10
 * 注:此方法无法处理增补字符。若要支持所有 Unicode 字符,包括增补字符,请使用 digit(int, int) 方法
 *
 * @param  要转换的字符(Unicode 代码点),一般由char强转的int,char '0'-'9' 对应 int 48-57
 * @param  基数
 * @return  使用指定基数的字符所表示的数值
 * @开始版本   1.5
 */
public static int digit(int codePoint, int radix) {  // ♆
    return CharacterData.of(codePoint).digit(codePoint, radix);  // ⚘
}

再看CharacterData.of(int ch) 源码,根据位运算选取CharacterData的实现类

char的值,如果在255以内,就调用CharacterDataLatin1来处理,再多一位二进制数字
用CharacterData00,然后CharacterData01,CharacterData02

static final CharacterData of(int ch) {  // ♆
    if (ch >>> 8 == 0) {     // fast-path
        return CharacterDataLatin1.instance;
    } else {
        switch(ch >>> 16) {  //plane 00-16
        case(0):
            return CharacterData00.instance;
        case(1):
            return CharacterData01.instance;
        case(2):
            return CharacterData02.instance;
        case(14):
            return CharacterData0E.instance;
        case(15):   // Private Use
        case(16):   // Private Use
            return CharacterDataPrivateUse.instance;
        default:
            return CharacterDataUndefined.instance;
        }
    }
}

以CharacterDataLatin1.digit(int ch, int radix) 为例,大概就是传的是 '0' - '9' 之间的字符,就返回相应的数字。

这块有些复杂,不是科班出身,需要脑补计算机基础,有兴趣的可以自行百度!

 int digit(int ch, int radix) {  // ♆
    int value = -1;
    if (radix >= Character.MIN_RADIX && radix <= Character.MAX_RADIX) {
        int val = getProperties(ch);  // ⚘
        int kind = val & 0x1F;
        if (kind == Character.DECIMAL_DIGIT_NUMBER) {
            value = ch + ((val & 0x3E0) >> 5) & 0x1F;
        }
        else if ((val & 0xC00) == 0x00000C00) {
            // Java supradecimal digit
            value = (ch + ((val & 0x3E0) >> 5) & 0x1F) + 10;
        }
    }
    return (value < radix) ? value : -1;
}


int getProperties(int ch) { // ♆
    char offset = (char)ch;
    int props = A[offset];
    return props;
}

static final int A[] = new int[256];   // 2^8=256

static final String A_DATA =  
    "\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800"+  
    // 省略....  
    "\u061D\u7002"; 

static {
    { 这个代码是GenerateCharacter自动创建;这块静态代码块里写构造代码块的具体目的?
        char[] data = A_DATA.toCharArray();
        assert (data.length == (256 * 2));
        int i = 0, j = 0;
        while (i < (256 * 2)) {
            int entry = data[i++] << 16;
            A[j++] = entry | data[i++];     // 这块 i 和 j 的关系 : i = j * 2
        }
    }

} 

static final String A_DATA =  
    "\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800"+  
    // 省略....  
    "\u061D\u7002"; 

可以将上面代码单独拿出来自行测试,看看运算过程:

public static void main(String[] args) {
        System.out.println( (char)57);
        System.out.println(Character.digit(57,10));
//      a();
    }
    
    public static  void a() {
        
        System.out.println(87 >>> 8);
        
        String A_DATA =  "\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800\u100F\u4800"+
                // 省略。。。
                "\u061D\u7002";
        
        int A[] = new int[256];
        char[] data = A_DATA.toCharArray();
        assert (data.length == (256 * 2));
        int i = 0, j = 0;
        while (i < (256 * 2)) {
            int entry = data[i++] << 16;
            char a = data[i++] ;
            System.out.println(j+"-"+i+":   "+(entry | a));
            A[j++] = entry | a;
        }
        System.out.println((int)'1');
        System.out.println( A['1'] & 0x1F);
        System.out.println((int)'1'  + ((A['1']  & 0x3E0) >> 5) & 0x1F);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352

推荐阅读更多精彩内容