Integer类主要的作用就是对基本类型int进行封装,提供了一些处理int类型的方法,比如int到String类型的转换方法或String类型到int类型的转换方法,当然也包含与其他类型之间的转换方法。除此之外还有一些位相关的操作 。
说明:该源码来自于 jdk_1.8.0_162 版本。
IntegerCache 内部类
说明:IntegerCache 是 Integer 的一个内部类,默认缓存的范围是 [-128,127],当Integer的值范围在 [-128,127] 时则直接从缓存中获取对应的Integer对象,不必重新实例化。这些缓存值都是静态且 final 的,避免重复的实例化和回收。另外我们可以改变这些值缓存的范围,在启动JVM时通过:-Djava.lang.Integer.IntegerCache.high=xxx 就可以改变缓存值的最大值,比如:-Djava.lang.Integer.IntegerCache.high=500 则会缓存 [-128,500] 。
注意:新建 Integer对象时尽量使用 Integer.valueOf 方法以优化性能。
我们通过两道面试题来了解一下这个缓存
第一题:
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
System.out.println("交换前:" + "a = " + a + " " + "b = " + b);
swap(a, b);
System.out.println("交换后:" + "a = " + a + " " + "b = " + b);
}
private static void swap(Integer numA, Integer numB) {
Integer temp = numA;
numA = numB;
numB = temp;
}
输出结果:
交换前:a = 1 b = 2
交换后:a = 1 b = 2
注意:关于值传递和引用传递的问题 。这里要特殊考虑String,以及Integer、Double等基本类型包装类,它们的类前面都有final修饰,为不可变的类对象,每次操作(new或修改值)都是新生成一个对象,对形参的修改时,实参不受影响,与值传递的效果类似,但实际上仍是引用传递。
说明:Integer a = 1 实际上等价于 Integer a = Integer.valueOf(1) ,这就是自动装箱(后面会详解) 。尽管 a 和 b 都是 Integer 类型的对象,但是由于 Integer 这个类本身就跟 String 一样,是用 final 修饰的,并且 value 属性也是 final 修饰的,所以无论是调用方法重新赋值或者直接在 main 方法重新赋值,都会产生一个新的对象,原来的那个对象不会做任何改变,表面上都是同一个变量名,实际上却是一个新的对象 。
我们来看一个伪代码:
Integer a = Integer.valueOf(5);
a = Integer.valueOf(10);
等价于
Integer a = Integer.valueOf(5);
Integer newA = Integer.valueOf(10);
a = newA;
结论:所以常规的方法不能对他们原始的对象做任何的改变,在调用 swap() 方法时,实际上 numA 和 numB 的改变跟 a 和 b 并没有任何关系,因为 numA 和 numB 一旦修改,所谓改变后的对象那都是新建的对象,所以无论 numA 和 numB 如何改变,都不会影响 a 和 b 的值 。那么如果我们想改变 a 和 b 的值怎么办呢,那就是绕过这些 final 和 private 的限制,这里我们想到了反射,接着看第二题 。
第二题:
public static void main(String[] args) throws Exception {
Integer a = 1;
Integer b = 2;
System.out.println("交换前:" + "a = " + a + " " + "b = " + b);
swap(a, b);
System.out.println("交换后:" + "a = " + a + " " + "b = " + b);
Integer c = 1;
System.out.println("c = " + c);
}
private static void swap(Integer numA, Integer numB) throws Exception {
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true);
int temp = numA;
field.set(numA, numB);
field.set(numB, temp);
}
输出结果:
交换前:a = 1 b = 2
交换后:a = 2 b = 2
c = 2
说明:这一次我们使用反射来尝试替换 a 和 b 的值,但是看到运行结果,可能会疑惑:
(1) 为什么交换后 a = 2 b =2 ?
(2) 为什么 c = 2 ?
要解开这些疑问,我们得先把 Integer 的拆箱装箱的问题解决,拆箱装箱是Java语言的语法糖,为了看到最终运行时的实际代码,最简单的方式就是直接反编译看编译过后的代码,我们用 jad 工具反编译上述的代码得到如下内容:
public static void main(String args[]) throws Exception {
Integer a = Integer.valueOf(1); //自动装箱
Integer b = Integer.valueOf(2); //自动装箱
System.out.println((new StringBuilder()).append("a = ").append(a).append("b = ").append(b).toString());
swap(a, b);
System.out.println((new StringBuilder()).append("a = ").append(a).append("b = ").append(b).toString());
Integer c = Integer.valueOf(1);
System.out.println((new StringBuilder()).append("c = ").append(c).toString());
}
private static void swap(Integer numA, Integer numB) throws Exception {
Field field = java/lang/Integer.getDeclaredField("value");
field.setAccessible(true);
int temp = numA.intValue(); //自动拆箱
field.set(numA, numB);
field.set(numB, Integer.valueOf(temp));
}
说明:看到反编译后的代码,验证了我们上面说的自动装箱拆箱操作 。通过我们对 IntegerCache 内部类的学习可以知道,Integer.valueOf(1) 和 Integer.valueOf(1) 实际上获取的是 Integer cache[] 里面的值,具体是 cache 数组里面的哪个值呢,我们来看 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);
}
说明:通过这个方法我们可以算出来 Integer.valueOf(1) = IntegerCache.cache[129] ,我们通过查看 IntegerCache 的静态代码块可以知道,IntegerCache.cache[129] = new Integer(1),也就是 value 为 1 的Integer 对象,但是这个 Integer 对象是存在 cache[] 数组缓存里面的 。既然我们知道了 -128 ~ 127 是使用的缓存里面的 Integer 对象,那么我们进一步将代码中的涉及到缓存的 Integer 都替换成缓存对象,添加上注释再来看看执行的流程:
public static void main(String args[]) throws Exception {
Integer a = Integer.valueOf(1); //自动装箱,Integer.valueOf(1) = IntegerCache.cache[129]
Integer b = Integer.valueOf(2); //自动装箱,Integer.valueOf(2) = IntegerCache.cache[130]
System.out.println((new StringBuilder()).append("a = ").append(a).append("b = ").append(b).toString());
swap(a, b);
System.out.println((new StringBuilder()).append("a = ").append(a).append("b = ").append(b).toString());
Integer c = Integer.valueOf(1); //自动装箱,Integer.valueOf(1) = IntegerCache.cache[129] 经过 swap 方法的修改,此时 value 为 2
System.out.println((new StringBuilder()).append("c = ").append(c).toString());
}
private static void swap(Integer numA, Integer numB) throws Exception {
Field field = java/lang/Integer.getDeclaredField("value");
field.setAccessible(true);
int temp = numA.intValue();
//numA = Integer.valueOf(1) = IntegerCache.cache[129] ,其 value 为 1
//numB = Integer.valueOf(2) = IntegerCache.cache[130] ,其 value 为 2
//这一步是将 IntegerCache.cache[129] 设置为 IntegerCache.cache[130]
field.set(numA, numB);
//numB = Integer.valueOf(2) = IntegerCache.cache[130],其 value 为 2
//经过上一步的修改,此时 Integer.valueOf(temp) = Integer.valueOf(1) = IntegerCache.cache[129],其 value 为 2
//这一步是将 IntegerCache.cache[130] 设置为 IntegerCache.cache[129],IntegerCache.cache[130],其 value 依旧为 2
field.set(numB, Integer.valueOf(temp));
}
说明:也就是说,field.set(numA, numB) 这一步实际上修改的是 IntegerCache.cache[129] 的值,而 IntegerCache.cache 是全局的,所以会影响到后续使用到这个缓存值的所有代码的运行结果 。这也就解释了输出结果: a = 2 b = 2 以及 c = 2 的原因了 。但这么做风险太大,实际项目中不能这么做 。想要完成期望的结果,也就是:a = 2 b = 1,可以使用 field.setInt(numB, Integer.valueOf(temp)) 或者 field.set(numB, new Integer(1)) 这个做法,但是这么做的话,被修改成错误的 cache 值又多了一个,最终,在错误的道路上又前进了一步 。
parseInt 方法
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.
*/
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;
int multmin;
int digit;
if (len > 0) {
char firstChar = s.charAt(0);
if (firstChar < '0') { // Possible leading "+" or "-"
if (firstChar == '-') {
negative = true;
limit = Integer.MIN_VALUE;
} else if (firstChar != '+')
throw NumberFormatException.forInputString(s);
if (len == 1) // Cannot have lone "+" or "-"
throw NumberFormatException.forInputString(s);
i++;
}
multmin = limit / radix;
while (i < len) {
// Accumulating negatively avoids surprises near MAX_VALUE
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 {
throw NumberFormatException.forInputString(s);
}
return negative ? result : -result;
}
使用方法:
(1) Integer.parseInt("100",10) 表示十进制的100,所以值为100。
(2) Integer.parseInt("100",2) 表示二进制的100,所以值为4。
(3) Integer.parseInt("666",2) 会抛出java.lang.NumberFormatException异常,因为 "666" 不符合二进制数规则。
(4) Integer.parseInt("10000000000",10) 会抛出java.lang.NumberFormatException异常,因为该数值大于Integer的最大值。
bitCount 方法
public static int bitCount(int i) {
// HD, Figure 5-2
i = i - ((i >>> 1) & 0x55555555);
i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
i = (i + (i >>> 4)) & 0x0f0f0f0f;
i = i + (i >>> 8);
i = i + (i >>> 16);
return i & 0x3f;
}
作用:计算二进制数中1的个数 。
核心思想:核心思想就是先每两位一组求和看有多少个1,然后在前面的基础上再算每四位一组求和看有多少个1,接着每8位一组求和看有多少个1,接着16位,32位,最终在与0x3f进行与运算,得到的数即为1的个数。依次扩大组的范围,将前面的计算结果再次合并在一起,最终将全部32位所包含的1都计算出来 。
特殊值:
0x55555555 = 01010101010101010101010101010101
0x33333333 = 110011001100110011001100110011
0x0f0f0f0f = 1111000011110000111100001111
0x3f = 0011 1111 = 63 (除去符号位,最多只能有63个1)
需求:以 i = 10011111 为例,计算 10011111 中 1 的个数 。
(1) 第一步:i = i - ((i >>> 1) & 0x55555555);
这段代码的作用是:以两位数字为一组,统计 1 的个数,结果为: 1个 1个 2个 2个
转换为二进制表示为:01 01 10 10 ,所以经过这一步后:i = 01011010
(2) 第二步:i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
这段代码的作用是:将上面计算出来的结果 01011010 以4位数字为一组,统计 1 的个数,结果为: 2个 4个
转换为二进制表示为:0010 0100 ,所以经过这一步后:i = 00100100
(3) 第三步:i = (i + (i >>> 4)) & 0x0f0f0f0f;
这段代码的作用是:将上面计算出来的结果 00100100 以8位数字为一组,统计 1 的个数,结果为: 6个
转换为二进制表示为:00000110 ,所以经过这一步后:i = 00000110
(4) 第四步:i = i + (i >>> 8);
这段代码的作用是:将上面计算出来的结果 00000110 以16位数字为一组,统计 1 的个数,结果为: 6个
经过这一步后依旧为:i = 00000110
(5) 第五步:i = i + (i >>> 16);
这段代码的作用是:将上面计算出来的结果 00000110 以32位数字为一组,统计 1 的个数,结果为: 6个
经过这一步后依旧为:i = 00000110
(6) 最后一步:i & 0x3f;
这段代码的作用是:个人理解,求绝对值并且保证数值不会越界
00000110 & 00111111 = 00000110 = 6 个
highestOneBit 方法
public static int highestOneBit(int n) {
// HD, Figure 3-1
n |= (n >> 1);
n |= (n >> 2);
n |= (n >> 4);
n |= (n >> 8);
n |= (n >> 16);
return n - (n >>> 1);
}
作用:获取 i 数值的二进制中最高位的 1 ,其他全为0的值。(例如:输入 101011 ,返回 100000 )
核心思想:找到数值 n 的最高位的 1 ,将最高位 1 的后面的所有二进制数值都变为 1 (例如:0010 1001 变为 0011 1111),然后将获取到的数值右移一位(0011 1111 变为 0001 1111),与原来的数值相减(0011 1111 - 0001 1111),便可以得到最高位 1 后面全为 0 的二进制数。
假设 n = 9,其中 第3 ~ 7行的操作图解为(目的就是将最高位 1 的后面的所有二进制数值都变为 1):
最后一步 n - (n >>> 1) 为:
最终结果为:0000 1000 。
lowestOneBit 方法
public static int lowestOneBit(int n) {
// HD, Section 2-1
return n & -n;
}
作用:与 highestOneBit 方法相反,lowestOneBit 方法获取的是获取最低位的 1,其他全为0的值 。
核心思想:我们都知道,在计算机中,负数以其正值的补码形式表达。利用 n 的原码与补码进行 & 运算,正好能够去掉其他位置的1,留下最低位的 1 。
运算过程:
说明:假设 n = 5,那么使用二进制数表示为(int类型32位):00000000 00000000 00000000 00000101 。
原码:00000000 00000000 00000000 00000101 (一个整数,按照绝对值大小转换成的二进制数)
反码:11111111 11111111 11111111 11111010 (将二进制数按位取反所得到的二进制数)
补码:11111111 11111111 11111111 11111011 (反码加1称为补码)
最后 n & -n就是如下运算:
看到这里我们可能还是会有一个疑问,为什么 原码 & 补码 正好就得到最低位的 1 后面全为 0 的二进制数?
解答:如上所示,如果我们直接将 n = 5 的原码和反码做 & 运算,那么结果将为 0 ,所以关键点在于这个对反码的加 1 操作,我们都知道,反码就是将原码取反过来显示,那也就意味着原码原来是 0 的地方都会变成 1,而原来是 1 的地方都会变成 0,而这个加 1 操作正好就作用到原码的最低位 1 这个位置,我们来看一个例子:
n = 4
原码:0000 0100
反码:1111 1011
反码 + 1 :1111 1011 + 0000 0001 = 1111 1100 (补码)
我们可以看到,由于原码的 1 后面是 00,在取反码的时候 00 会变成 11,而原码的 1 取反后会变成 0,也就是 1111 1011,当再给这个 反码 + 1 的时候,这个 0 能够起到一个截止的作用,也就是正好停在了原码的最低位的 1 的地方,现在我们再来看这个补码的组成特点:
这样的一个数与原码做 & 运算,结果当然就是最低位的 1 左边的数全部变为 0,而只保留最低位的 1 了。
numberOfLeadingZeros 方法
public static int numberOfLeadingZeros(int i) {
// HD, Figure 5-6
if (i == 0)
return 32;
int n = 1;
if (i >>> 16 == 0) { n += 16; i <<= 16; }
if (i >>> 24 == 0) { n += 8; i <<= 8; }
if (i >>> 28 == 0) { n += 4; i <<= 4; }
if (i >>> 30 == 0) { n += 2; i <<= 2; }
n -= i >>> 31;
return n;
}
作用:计算 i 的二进制从头开始有多少个连续的 0 。
核心思想:这里处理其实是体现的是二分查找思想,先看高16位是否为0,是的话将高16位的0全部去掉,把低16位的数值补上,并记录下0的个数,继续往下判断,否则接着右移24位看是不是为0,这一步的目的是看高8位是否为0,是的话继续叠加上一次判断得到的 0 的个数,并且把高8位的0去掉,补上低24位的数值,否则继续往下判断,直到最后得到结果 。
运算过程:
说明: i <<= 16 等价于 i = i << 16;
假设 i = 1257084 = 0000 0000 0001 0011 0010 1110 0111 1100
(1) 第一步:if (i >>> 16 == 0) { n += 16; i <<= 16; }
0000 0000 0001 0011 0010 1110 0111 1100 无符号右移16位结果为: 0000 0000 0000 0000 0000 0000 0001 0011; 结果不为 0,所以继续往下判断。
(2) 第二步:if (i >>> 24 == 0) { n += 8; i <<= 8; }
0000 0000 0001 0011 0010 1110 0111 1100 无符号右移24位结果为: 0000 0000 0000 0000 0000 0000 0000 0000; 结果为 0。
接下来将 n 的值由 1 叠加上 8 ,i 左移 8 位,也就是将高8位的0全部去掉,把低24位的数值左移补上,左右边补 8 个 0,此时结果为 :
n = 9;
i = 0001 0011 0010 1110 0111 1100 0000 0000;
(3) 第三步:if (i >>> 28 == 0) { n += 4; i <<= 4; }
0001 0011 0010 1110 0111 1100 0000 0000 无符号右移28位结果为: 0000 0000 0000 0000 0000 0000 0000 0001; 结果不为 0,所以继续往下判断。
(4) 第四步:if (i >>> 30 == 0) { n += 2; i <<= 2; }
0001 0011 0010 1110 0111 1100 0000 0000 无符号右移30位结果为: 0000 0000 0000 0000 0000 0000 0000 0000; 结果为 0。
接下来将 n 的值由 9 叠加上 2 ,i 左移 2 位,也就是将高2位的0全部去掉,把低30位的数值左移补上,左右边补 2 个 0,此时结果为 :
n = 11;
i = 0100 1100 1011 1001 1111 0000 0000 0000;
(5) 第五步:n -= i >>> 31;
0100 1100 1011 1001 1111 0000 0000 0000 无符号右移31位结果为: 0000 0000 0000 0000 0000 0000 0000 0000; 结果为 0。
这一步是为了判断最高位是否为 1,如果为 1 则减去最开始的 n 的初始值 1,否则 n 就是最终结果。
numberOfTrailingZeros 方法
public static int numberOfTrailingZeros(int i) {
// HD, Figure 5-14
int y;
if (i == 0) return 32;
int n = 31;
y = i <<16; if (y != 0) { n = n -16; i = y; }
y = i << 8; if (y != 0) { n = n - 8; i = y; }
y = i << 4; if (y != 0) { n = n - 4; i = y; }
y = i << 2; if (y != 0) { n = n - 2; i = y; }
return n - ((i << 1) >>> 31);
}
作用:计算 i 的二进制从末尾开始有多少个连续的 0 。
说明:numberOfTrailingZeros 方法和 numberOfLeadingZeros 方法是完全相反的操作,不仅仅移位是相反的,就连 n 的值的处理方式也是反的,n 的初始值为 31,然后通过判断来减小,理解了上一个方法,这个方法就不做过多解释了。
toUnsignedString0 方法
//十进制转换为二进制
public static String toBinaryString(int i) {
return toUnsignedString0(i, 1);
}
//十进制转换为八进制
public static String toOctalString(int i) {
return toUnsignedString0(i, 3);
}
//十进制转换为十六进制
public static String toHexString(int i) {
return toUnsignedString0(i, 4);
}
private static String toUnsignedString0(int val, int shift) {
// assert shift > 0 && shift <=5 : "Illegal shift value";
//获取val的有效位数
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);
}
//解析出每个字符
static int formatUnsignedInt(int val, int shift, char[] buf, int offset, int len) {
int charPos = len;
int radix = 1 << shift;
int mask = radix - 1;
do {
buf[offset + --charPos] = Integer.digits[val & mask];
val >>>= shift;
} while (val != 0 && charPos > 0);
return charPos;
}
作用:将十进制数转换为其他进制的字符串值 。