资讯专栏INFORMATION COLUMN

java.lang.Integer 源码深入解读

mingzhong / 1146人阅读

摘要:最近算是比较深入的了解了一下的源码,就想着写点东西记录一下,一来可以加深理解,再来也算是为我刷了那么久平台贡献一点自己的绵薄之力。这两个方法都是给当前的实例的属性赋值,参数为类型的构造器直接将参数赋值给属性,参数为是将方法的返回值赋值。

最近算是比较深入的了解了一下Integer的源码,就想着写点东西记录一下,一来可以加深理解,再来也算是为我刷了那么久segmentfault平台贡献一点自己的绵薄之力。

一、构造函数:
解读一个类的源码我喜欢从构造函数入手,这里先上Integer的构造源码:

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

Integer类中提供了两个构造函数,分别针对构造参数为基本类型int和引用类型String。这两个方法都是给当前的实例的value属性赋值,参数为int类型的构造器直接将参数赋值给value属性,参数为String是将parseInt(String s, int radix)方法的返回值赋值。JDK1.5之后,java提供了自动装箱和自动拆箱的功能。自动装箱也就是调用了Integer类的一个静态方法valueOf方法,先看源码:

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

源码中有一个IntegerCache,这一个私有的内部类。这个类缓存了low - high之间数字的包装类。关于这个类的分析在下面,反正你需要记住它把一些数字的包装类提前缓存了,如果判断成立就把缓存中的那个包装类返回,如果不则new一个新的。这里也就明白了下面问题的原因了:

Integer a = 100;
Integer b = 100;

Integer c = 200;
Integer d = 200;
        
System.out.println(a == b);//true
System.out.println(c == d);//false

通过javap -c/javap -verbose 命令可以查看字节码;红色圈圈里就是我们jdk5之后的基本类型的自动包装的字节码实现,可以看出,此处是调用了Integer.valueOf(..)方法的:说白了就是Integer a = 100 等价于Integer a = Integer.valueOf(100)

二、IntegerCache:
先上源码:

private static class IntegerCache {
  static final int low = -128;
  static final int high;
  static final Integer cache[];
  
  static {
      // high value may be configured by property
      int h = 127;
      String integerCacheHighPropValue =
          sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
      if (integerCacheHighPropValue != null) {
          int i = parseInt(integerCacheHighPropValue);
          i = Math.max(i, 127);
          // Maximum array size is Integer.MAX_VALUE
          h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
      }
      high = h;
  
      cache = new Integer[(high - low) + 1];
      int j = low;
      for(int k = 0; k < cache.length; k++)
          cache[k] = new Integer(j++);
  }
  
  private IntegerCache() {}
  }

以上可以知道这个类是私有的且是静态的,并且他有三个被final修饰的静态filed外加一个静态块和一个私有的构造器;很简单很普通的一个类,被缓存的包装类就介于low - high之间,low的值已经写死-128,而high的值由你的虚拟机决定sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"),既然是一个参数也就意味着你可以动态设置,具体怎么设置自行百度。然后在循环中将low - high之间数字的装箱后方法cache[]这个Integer类型的数组中。这样就完成了缓存。

三、toString(int i): 照例先上源码:

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

这个方法内部调用了两个方法:stringSizegetChars,toString方法的实现也就是由这两个方法合作完成,stringSize实现上很是比较简单的,源码我就不上了,它是用来计算参数i的位数也就是转成字符串之后的字符串的长度。内部结合一个已经初始化好的int类型的数组sizeTable来完成这个计算。稍动头脑就能想明白,很巧妙的一个方法。然后来说说toString这个方法实现的最大功臣getChars这个方法,上源码:

 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));
        i = q;
        buf [--charPos] = DigitOnes[r];
        buf [--charPos] = DigitTens[r];
    }

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

三个参数:i:被初始化的数字,index:这个数字的长度(包含了负数的符号“-”),buf:字符串的容器-一个char型数组。第一个if判断,如果i<0,sign记下它的符号“-”,同时将i转成整数。下面所有的操作也就只针对整数了,最后在判断sign如果不等于零将sig你的值放在char数组的首位buf [--charPos] = sign;。 最后来分析方法中的两个循环:whilefor,其实这两个循环做的事情一样。只是while循环来处理i>65535的情况,且每次取两位数:

buf [--charPos] = DigitOnes[r];
buf [--charPos] = DigitTens[r];

剩下的情况由for循环处理,且每次去一个数字。至于为什么这么做:// Fall thru to fast mode for smaller numbers,这是官方注释,意思就是真对小的数字使用快速方式。针对这块的理解我也是参考了知乎上的网友的回答java源码中Integer.class中有个getChars方法,里面有个52429是怎么确定的? 表示感谢。

至此Integer类的核心也就完了。就这吧!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66067.html

相关文章

  • 深入浅出了解“装箱与拆箱”

    摘要:本章部分内容从源码中解读一些自动装箱与拆箱的原理,以及会出现的一些陷阱已经性能等。例题分析我们通过几个经典的问题,来看看大家到底理解了装箱与拆箱的知识点没。 showImg(https://img-blog.csdnimg.cn/20190426221838971.gif);showImg(https://img-blog.csdnimg.cn/20190426221918208.pn...

    FullStackDeveloper 评论0 收藏0
  • 为何Spring MVC可获取到方法参数名,而MyBatis却不行?【享学Spring MVC】

    每篇一句 胡适:多谈些问题,少聊些主义 前言 Spring MVC和MyBatis作为当下最为流行的两个框架,大家平时开发中都在用。如果你往深了一步去思考,你应该会有这样的疑问: 在使用Spring MVC的时候,你即使不使用注解,只要参数名和请求参数的key对应上了,就能自动完成数值的封装 在使用MyBatis(接口模式)时,接口方法向xml里的SQL语句传参时,必须(当然不是100%的必须,...

    孙淑建 评论0 收藏0
  • Java源码阅读笔记之Integer

    摘要:估计这就是推荐使用的主要原因吧正负标识判断输入的字符串是否为开头转化逻辑字符串转化为的关键在于数组,以进制为例,用表示到,满才会进。 Integer的基本实现Integer的使用Integer封装的操作 Integer的基本实现 基本描述:Integer是对原生基本类型int的封装,其定义value来存储值和一些用于描述int的信息 int value;//int int SIZE...

    wenzi 评论0 收藏0
  • 详叙BeanWrapper和PropertyDescriptor

    摘要:关于它的数据转换使用了如下两种机制隶属于规范。这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进中,这种对象称为值对象,或。 每篇一句 千古以来要饭的没有要早饭的,知道为什么吗? 相关阅读 【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、Pro...

    APICloud 评论0 收藏0
  • JVM执行方法调用(一)- 重载与重写

    摘要:重写语言中的定义子类方法有一个方法与父类方法的名字相同且参数类型相同。父类方法的返回值可以替换掉子类方法的返回值。思维导图参考文档极客时间深入拆解虚拟机是如何执行方法调用的上广告 原文 回顾Java语言中的重载与重写,并且看看JVM是怎么处理它们的。 重载Overload 定义: 在同一个类中有多个方法,它们的名字相同,但是参数类型不同。 或者,父子类中,子类有一个方法与父类非私有方...

    韩冰 评论0 收藏0

发表评论

0条评论

mingzhong

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<