integer 类在对象中包装了一个基本类型 int 的值。integer 类型的对象包含一个 int 类型的字段。
此外,该类提供了多个方法,能在 int 类型和 String 类型之间互相转换,还提供了处理 int 类型时非常有用的其他一些常量和方法。
public final class Integer extends Number implements Comparable<Integer>
从类定义中我们可以知道以下几点:
1、Integer类不能被继承
2、Integer类实现了Comparable接口,所以可以用compareTo进行比较并且Integer对象只能和Integer类型的对象进行比较,不能和其他类型比较(至少调用compareTo方法无法比较)。
3、Integer继承了Number类,所以该类可以调用longValue、floatValue、doubleValue等系列方法返回对应的类型的值。
一、私有属性
Integer类中定义了以下几个私有属性:
还有一个私有属性——value属性就是Integer对象中真正保存int值的。
当我们使用new Integer(10)
创建一个Integer对象的时候,就会用以下形式给value赋值。还有其他的构造函数在后面会讲。
这里我们讨论一下Interger对象的可变性。从value的定义形式中可以看出value被定义成final类型。也就说明,一旦一个Integer对象被初始化之后,就无法再改变value的值。那么这里就深入讨论一下以下代码的逻辑:
在以上代码中,首先调用构造函数new一个Integer对象,给私有属性value赋值,这时value=10
,接下来使用i=5
的形式试图改变i的值。有一点开发经验的同学都知道,这个时候如果使用变量i,那么它的值一定是5,那么i=5
这个赋值操作到底做了什么呢?到底是如何改变i的值的呢?是改变了原有对象i中value的值还是重新创建了一个新的Integer对象呢?
我们将上面的代码进行反编译,反编译之后的代码如下:
通过看反编译之后的代码我们发现,编译器会把i=5
转成i = Integer.valueOf(5);
这里先直接给出结论,i=5操作并没有改变使用Integer i = new Integer(10);
创建出来的i中的value属性的值。要么是直接返回一个已有对象,要么新建一个对象。这里的具体实现细节在后面讲解valueOf
方法的时候给出。
二、公共属性
以上属性可直接使用,因为他们已经定义成publis static fianl
能用的时候尽量使用他们,这样不仅能使代码有很好的可读性,也能提高性能节省资源。
构造方法
Integer提供了两个构造方法:
从构造方法中我们可以知道,初始化一个Integer对象的时候只能创建一个十进制的整数。
Integer valueOf(int i)方法
前面说到Integer中私有属性value的时候提到
其中i=5
操作时,编译器会转成i = Integer.valueOf(5);
执行。那么这里就解释一下valueOf(int i)方法是如何给变量赋值的。
以上是valueOf方法的实现细节。通常情况下,IntegerCache.low=-128,IntegerCache.high=127(除非显示声明java.lang.Integer.IntegerCache.high的值),Integer中有一段动态代码块,该部分内容会在Integer类被加载的时候就执行。
也就是说,当Integer被加载时,就新建了-128到127的所有数字并存放在Integer数组cache中。
再回到valueOf代码,可以得出结论。当调用valueOf方法(包括后面会提到的重载的参数类型包含String的valueOf方法)时,如果参数的值在-127到128之间,则直接从缓存中返回一个已经存在的对象。如果参数的值不在这个范围内,则new一个Integer对象返回。
所以,当把一个int变量转成Integer的时候(或者新建一个Integer的时候),建议使用valueOf方法来代替构造函数。或者直接使用Integer i = 100;
编译器会转成Integer s = Integer.valueOf(10000);
String转成Integer(int)的方法
以上所有方法都能实现将String类型的值转成Integer(int)类型(如果 String 不包含可解析整数将抛出NumberFormatException)
可以说,所有将String转成Integer的方法都是基于parseInt方法实现的。简单看一下以上部分方法的调用栈。
确定具有指定名称的系统属性的整数值。 第一个参数被视为系统属性的名称。通过System.getProperty(java.lang.String)
方法可以访问系统属性。然后,将该属性的字符串值解释为一个整数值,并返回表示该值的 Integer 对象。使用 getProperty
的定义可以找到可能出现的数字格式的详细信息。其中参数nm应该在System的props中可以找到。这个方法在日常编码中很好是用到。在代码中可以用以下形式使用该方法:
另外两个方法
第二个参数是默认值。如果未具有指定名称的属性,或者属性的数字格式不正确,或者指定名称为空或 null,则返回默认值。
getInteger的具体实现细节如下:
先按照nm作为key从系统配置中取出值,然后调用Integer.decode方法将其转换成整数并返回。
该方法的作用是将 String 解码为 Integer。接受十进制、十六进制和八进制数字。
根据要解码的 String(mn)的形式转成不同进制的数字。 mn由三部分组成:符号、基数说明符和字符序列。 —0X123
中-
是符号位,0X
是基数说明符(0表示八进制,0x,0X,#表示十六进制,什么都不写则表示十进制),123
是数字字符序列。
使用例子举例如下:
decode方法的具体实现也比较简单,首先就是判断String类型的参数mn是否以(+/—)符号开头。然后再依次判断是否以”0x”、“#”、“0”开头,确定基数说明符的值。然后将字符串mn进行截取,只保留其中纯数字部分。在用截取后的纯数字和基数调用valueOf(String s, int radix)
方法并返回其值。
返回一个 Integer 对象。如果指定第二个参数radix,将第一个参数解释为用第二个参数指定的基数表示的有符号整数。如果没指定则按照十进制进行处理。
该方法实现非常简单:
主要用到了两个方法,parseInt(String s, int radix)
和valueOf(int i)
方法。前面已经讲过valueOf方法会检查参数内容是否在-127到128之间,如果是则直接返回。否则才会新建一个对象。
使用第二个参数指定的基数(如果没指定,则按照十进制处理),将字符串参数解析为有符号的整数。除了第一个字符可以是用来表示负值的 ASCII 减号 ‘-‘ (‘\u002D’)外,字符串中的字符必须都是指定基数的数字(通过 Character.digit(char, int) 是否返回一个负值确定)。返回得到的整数值。
如果发生以下任意一种情况,则抛出一个 NumberFormatException
类型的异常:
第一个参数为 null 或一个长度为零的字符串。
基数小于 Character.MIN_RADIX 或者大于 Character.MAX_RADIX。
假如字符串的长度超过 1,那么除了第一个字符可以是减号 ‘-‘ (‘u002D’) 外,字符串中存在任意不是由指定基数的数字表示的字符.
字符串表示的值不是 int 类型的值。
示例:
该方法的具体实现方式也比较简单,主要逻辑代码(省略部分参数校验)如下:
主要思想其实也很好理解。
“12345”按照十进制转成12345的方法其实就是以下方式: ((1*10)+2)*10)+3)*10+4)*10+5
具体的如何依次取出“12345”中的每一个字符并将起转成不同进制int类型则是Character.digit方法实现的,这里就不深入讲解了。
上面列举了很多能够将String转成Integer的方法。那么他们之间有哪些区别,又该如何选择呢?
parseInt方法返回的是基本类型int
其他的方法返回的是Integer
valueOf(String)方法会调用valueOf(int)方法。
如果只需要返回一个基本类型,而不需要一个对象,可以直接使用Integert.parseInt("123");
如果需要一个对象,那么建议使用valueOf()
,因为该方法可以借助缓存带来的好处。
如果和进制有关,那么就是用decode
方法。
如果是从系统配置中取值,那么就是用getInteger
int转成String的方法
直接看toString方法,toString方法的定义比较简单,就是把一个int类型的数字转换成字符串类型,但是这个方法的实现调用了一系列方法,通过阅读这个方法,你就会对sun公司的程序员产生油然的敬佩。
我们把toString方法分解为以下几个片段:
片段一:
片段二:
片段三
片段四
这里先对i的值做检验,如果等于Int能表示的最小值,则直接返回最小值的字符串形式。那么为什么-2147483648要特殊处理呢?请看代码片段二的分析。
这段代码的主要目的是体取出整数i的位数,并创建一个字符数组。 其中提取I的位数使用stringSize方法,这个方法实现如下:
该方法要求传入一个正整数,如果传入的数字x的值是10000,那么因为他大于9,99,999,9999,小于99999.所以他会返回99999在整型数组sizeTable中的下标”4″+1 = 5。我们看10000这个数字的位数也确实是5。所以,就实现了返回一个正整数的位数。
设置size时,当i<0的时候返回的size数组在stringSize方法的基础上+1的目的是这一位用来存储负号。
由于stringSize方法要求传入一个正整数,所以代码片段二在调用该方法时需要将负数转成正数传入。代码片段一中,将-2147483648的值直接返回的原因就是整数最大只能表示2147483647,无法将
stringSize(-i)
中的i赋值成-2147483648。
1.局部性原理之空间局部性:sizeTable为数组,存储在相邻的位置,cpu一次加载一个块数据数据到cache中(多个数组数据),此后访问sizeTable 不需要访问内存。
2.基于范围的查找,是很实用的设计技术
那么接下来就深入理解一下getChars方法。这部分我把关于这段代码的分析直接写到注释中,便于结合代码理解。
接下来分析两个问题:
部分一
部分二
使用num1,num2,num3三个变量代替源代码中的数字,便于后面分析使用。
回答上面两个问题之前,首先要明确两点:
移位的效率比直接乘除的效率要高
乘法的效率比除法的效率要高
先理解以下代码:
r = i - ((q << 6) + (q << 5) + (q << 2));
表示的其实是r = i - (q * 100);
,i-q*2^6 - q*2^5 - q*2^2
= i-64q-32q-4q
= i-100q
。
q = (i * num2) >>> (num3);
中,>>>
表示无符号向右移位。代表的意义就是除以2^num3。 所以q = (i * 52429) >>> (16+3);
可以理解为:q = (i * 52429) / 524288;
,那么就相当于 q= i * 0.1
也就是q=i/10
,这样通过乘法和向右以为的组合的形式代替了除法,能提高效率。
再来回答上面两个问题中,部分一和部分二中最大的区别就是部分一代码使用了除法,第二部分只使用了乘法和移位。因为乘法和移位的效率都要比除法高,所以第二部分单独使用了乘法加移位的方式来提高效率。那么为什么不都使用乘法加移位的形式呢?为什么大于num1(65536)的数字要使用除法呢?原因是int型变量最大不能超过(2^31-1)。如果使用一个太大的数字进行乘法加移位运算很容易导致溢出。那么为什么是65536这个数字呢?第二阶段用到的乘法的数字和移位的位数又是怎么来的呢?
我们再回答第二个问题。
既然我们要使用q = (i * num2) >>> (num3);的形式使用乘法和移位代替除法,那么n和m就要有这样的关系:
num2= (2^num3 /10 +1)
只有这样才能保证(i * num2) >>> (num3)结果接近于0.1。
那么52429这个数是怎么来的呢?来看以下数据:
2^10=1024, 103/1024=0.1005859375 2^11=2048, 205/2048=0.10009765625 2^12=4096, 410/4096=0.10009765625 2^13=8192, 820/8192=0.10009765625 2^14=16384, 1639/16384=0.10003662109375 2^15=32768, 3277/32768=0.100006103515625 2^16=65536, 6554/65536=0.100006103515625 2^17=131072, 13108/131072=0.100006103515625 2^18=262144, 26215/262144=0.10000228881835938 2^19=524288,