Java源码 : Int包装类 -- Integer

1. Integer类的基本信息

NOTE : 以JDK 1.8 为准,补充部分属性、方法在 JDK-1.6 / JDK-1.7中的变化说明;

  • 继承自: Number类
  • 实现了:Serializable和Comparable接口

Integer类的结构和方法可以划分为以下区域:

  1. 类定义、属性:7个属性,都很好理解;
  2. 核心方法:构造Integer的方法和一些核心方法;
  3. int -> String方法:将int数字转换各种进制的字符串 ;
  4. String -> int方法:将各种进制的字符串解析为int数字 ;
  5. unsigned -> signed:java的int是有符号的,需要提供处理未符号的其他进制的方法;
  6. signed -> unsigned:将无符号的其他进制字符串转换为有符号的int数字;

2. 前提基础

有符号和无符号

在计算机表中,正负数的表示可以使用有符号和无符号两种表示,
- Java使用的是有符号的十进制表示方法,最高位为符号位,所以表示范围为 - 2^31 到 2^31;
- 对于无符号表示并不是说无法表示负数,而是最高位也用来表示数本身,负数用“补码”形式表示;

无符号表示可以翻阅很多资料:http://blog.csdn.net/sunweixiang1002/article/details/53048080

自动装箱和拆卸

自动装箱和拆卸是Java的语法糖,之所以需要分析Integer的源码即因为我们在日常编程中有很多自动装箱和拆卸机制,这些会给我们带来一些“坑”;

16进制语法糖

Java编程允许我们用Oxxxxx这样的形式定义一个16进制数据,如 int x = Ox11 ,实际上x就是17,Java编译时会将Ox11翻译为17,所以Integer的进制转换方法,不需要提供类似 : toDeciamlInt(int i); 这样的方法。

3. 核心方法 源码分析

只看使用频率最高的方法,其他的如 int -> String/ String -> Int 、有符号到无符号、进制转换,这些不常用,有需要再看下源码即可。

3.1 属性

7个属性,1个添加自1.8,主要是提升一些效率,避免运行时计算;

// 在Number中实现了Serializable接口
// 所以子类自动实现Serializable接口
public final class Integer extends Number implements Comparable<Integer> {

    //最小值,表示 -2^31
    //使用二进制分析就是最高位外后面31个0
   @Native public static final int   MIN_VALUE = 0x80000000;

    //最大值,表示2^31 -1
    @Native public static final int   MAX_VALUE = 0x7fffffff;

    //类型缓存
    public static final Class<Integer>  TYPE = (Class<Integer>) Class.getPrimitiveClass("int");

    // JDK 1.5添加,表示int 数字的位长度为32位;
    @Native public static final int SIZE = 32;

    // JDK 1.8添加,计算好需要的字节长度,1字节8位,其实就是 4
    public static final int BYTES = SIZE / Byte.SIZE;

    //真正的值,final的不可以被动态改变
    private final int value;

     @Native 
    private static final long serialVersionUID = 1360826667806852920L;
}

3.2 常用的核心方法

之所以使用Integer对象,就是int作为基本类型不具有对象方法,使用Integer对象频率最高的即以下方法。
另外需要使用Integer对象的场景一般如下两种:

  1. 需要使用一个特殊的值(null)表示无法计算,程序需要自己处理,比如从CacheUtil提供incry方法,如果key不存在则不计算,那么可以返回Integer,如果key不存在返回null,业务判断null则从其他数据源加载值来计算;
  2. 作为Map的Key,这是最常用的场景,Map要求必须有hashCode和equals。
    // 这里定义了一个内部静态类,用来缓存[-128,127]之间的Integer对象;
    // 这个缓存是为了JSL的自动装箱机制提供的,可以通过VM参数:-XX:AutoBoxCacheMax=<size>来调整这个缓存范围
    private static class IntegerCache {
        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static {
            // cache的最大值可以通过属性和vm参数设置
            int h = 127;
            // 去VM这个类查询参数,看是否配置了AutoBoxCacheMax的值;
            String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
            if (integerCacheHighPropValue != null) {
                try {
                    int i = parseInt(integerCacheHighPropValue);
                    i = Math.max(i, 127);

                    // 检测配置的最大值不能超过 MAX_VALUE - 129;
                    h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                } catch( NumberFormatException nfe) {
                }
            }

            //下面就是提前初始化,new出这些Integer对象出来
            high = h;

              // 缓存的最小值无法修改是:-128,
            // 最大值不能超过 MAX_VALUE - 129 , 
            // 如果high超过了,那么数组分配的时候会抛出:NegativeArraySizeException
            cache = new Integer[(high - low) + 1];
            int j = low;
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

              //断言机制,JLS7 要求范围必须大于[-128, 127]
            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

    //JDK 1.5提供的方法,优先使用valueOf方法替代new方法;
    public static Integer valueOf(int i) {
          // 先判断是不是在缓存范围内,内部类直接用属性了。
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        //超出缓存范围,直接new一个    
        return new Integer(i);
    }

    public static Integer valueOf(String s, int radix) throws NumberFormatException {
        return Integer.valueOf(parseInt(s,radix));
    }

    public static Integer valueOf(String s) throws NumberFormatException {
        return Integer.valueOf(parseInt(s, 10));
    }

    // 一般情况下不推荐使用
    public Integer(int value) {
        this.value = value;
    }

    public Integer(String s) throws NumberFormatException {
        this.value = parseInt(s, 10);
    }

    // 缩小位数,会抛出高位内容
    public byte byteValue() {
        return (byte)value;
    }

    // 缩小位数,会抛弃高位内容
    public short shortValue() {
        return (short)value;
    }

     //非常常用的一个方法
    public int intValue() {
        return value;
    }

    //扩宽位数
    public long longValue() {
        return (long)value;
    }

     //扩宽位数
    public float floatValue() {
        return (float)value;
    }

    //扩宽位数
    public double doubleValue() {
        return (double)value;
    }

     //默认转换为10进制
    public String toString() {
        return toString(value);
    }


  /**
     * 仔细看看这个方法,非常简单,就是返回value值作为hashCode.
     * Integer.valueOf(1); -> 1;
     * */
    @Override
    public int hashCode() {
        return Integer.hashCode(value);
    }

    //JDK 1.8 抽象出来
    public static int hashCode(int value) {
        return value;
    }

    /**
     * equals很熟悉,原理还是比较value是否== 但是这里先判断了是不是Integer的;
     * 在代码中 Integer x = 1; x.equasl(1L); 这是能编译过去的,但是返回的是false;
     * 这会引发一系列问题,尤其是作为Map的Key,要注意,此类场景不常用,但是容易采坑;
     */
    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }


    //JDK 1.2 提供的比较接口,用来排序
    public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }

    // 1.7 新增
    public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }

核心方法不多,而且大部分都很简单,核心方法中有一段IntegerCache,这是第重点,是为了支持Java的autoboxing机制,提升自动装箱和拆箱的效率;

3.2.1 自动装箱和拆箱

Java的自动装箱和拆卸其实是一种语法糖,比如如下代码:

    Integer x = 1;
    // 编译后实际上的写法是:
    Integer x = Integer.valuof(1);

3.2.2 == 比较的问题

对象的 == 判断是判断的引用,那么Integer的 == 也不例外,但是自动装箱可能破坏我们的认知,比如如下代码:

    Integer x = 1;
    System.out.println(x == 1); // case 1-0 : true 自动拆箱
    System.out.println(1 == x); // case 1-1 : true 自动拆箱
    Integer y = 1000;
    System.out.println(y == 1000); // case 2 : true 自动拆箱
    System.out.println(y == new Integer(1000)); // case : false 没有拆箱

根据源码可以分析出:

  • X == 1 这个不管是拆箱还是装箱,== 都是true, 那么实际上是被装箱了还是拆箱了呢?
  • y == new Integer(10000) ,因为y被自动装箱了,而且IntegerCache还没有缓存,所以肯定是否false;
  • y == 1000 这个就比较疑惑了,如果对1000自动装箱,那么就是false,如果对y自动拆箱那么肯定就是true;

javap -c的反编译内容如下:

Code:
        ## 这三个指令就是Integer x = Integer.valueOf(1);
       0: iconst_1
       1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       4: astore_1

       ## 这几条指令就是System.out.println(x == 1);
       ## 很明显x == 1使用的是 x.intValue == 1 
       ## 也就是自动拆箱了
       5: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       8: aload_1
       9: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
      12: iconst_1
      13: if_icmpne     20
      16: iconst_1
      17: goto          21
      20: iconst_0
      21: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V

      ## 这几条指令就是System.out.println(1 == x);
      ## 这里也证明了是自动拆箱了
      24: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      27: iconst_1
      28: aload_1
      29: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
      32: if_icmpne     39
      35: iconst_1
      36: goto          40
      39: iconst_0
      40: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V

      ## 这里1000没有被预编译到常量池
      ## 先自动装箱 Integer y = Integer.valueOf(1000);
      43: sipush        1000
      46: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      49: astore_2

      ## 这里自动拆箱了,System.out.println(y.intValue == 1000);
      50: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      53: aload_2
      54: invokevirtual #4                  // Method java/lang/Integer.intValue:()I
      57: sipush        1000
      60: if_icmpne     67
      63: iconst_1
      64: goto          68
      67: iconst_0
      68: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V

      ## 这里编译时没有拆箱用比较的是integer的==
      71: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
      74: aload_2
      75: new           #6                  // class java/lang/Integer
      78: dup
      79: sipush        1000
      82: invokespecial #7                  // Method java/lang/Integer."<init>":(I)V
      85: if_acmpne     92
      88: iconst_1
      89: goto          93
      92: iconst_0
      93: invokevirtual #5                  // Method java/io/PrintStream.println:(Z)V
      96: return

NOTE : Java的编译器在Integer和int比较的时候使用的是拆箱机制,Integer的比较要使用equals不要使用 == ,虽然这在一定范围内可以被编译器优化为自动拆箱,并且因为缓存的原因有时是正确的,但是实际上很危险。

3.2.3 valueOf的使用

Integer在构造的时候推荐使用valueOf而不是new Integer,valueOf会首先判断是否需要缓存,也会造成如下的不同:

    Integer x = 120;
    Integer y = 120;
    Integer x2 = 150;
    Integer y2 = 150;
    System.out.println(x == y);(case 1 : true)
    System.out.println(x == new Integer(120)); (case 2 : false)
    System.out.println(x == Integer.valueOf(120)); (case 2 : true)
    System.out.println(x2 == y2) ; // case 4 : false
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值