int 类型是java中常见的基本类型。但是最近我发现在int的使用有一些不易掌握的特性,在不深入了解内部原理情况下,容易判断错误。
1、Integer 真是引用类型吗
众所周知,Integer作为int包装类是引用类型。但是请看下面这段代码,最后输出是true。
Integer a=127;
Integer b=127;
System.out.println(a==b);
2、-2147483648
在数学意义上,绝对值是非负数。但下面代码取绝对值的输出还是-2147483648
int tmp=Math.abs(-2147483648);
System.out.println(tmp);
以上问题涉及两个知识点
- IntegerCache
- 计算机加减法运算(原码、反码、补码)
把两个知识点展开看
- IntegerCache
JSL规定IntegerCache为Integer类的缓存类,默认缓存了-128~127的Integer 值,如遇到[-128,127]范围的值需要转换为Integer时会直接从IntegerCache中获取。同时,我们可以通过设置java.lang.Integer.IntegerCache.high来设置缓存最大值
-Djava.lang.Integer.IntegerCache.high=255
- 计算机加减法(原码、反码、补码)
- 原码、反码和补码
我们都知道计算机中的数值使用二进制表示的,与计算机存储结构有关。假定我们的计算机为4位机(一个Byte),那么十进制1在转为二进制就是0001。此外,我们还需要考虑一个问题那就是符合,负数我们怎么表示,我们将第一位设置为符号位(0为正数+,1为负数-)。这样我们的原码就出来了。
十进制 | 4位二进制原码 |
---|---|
1 | 0001 |
-2 | 1010 |
考虑到计算机特性,运算方式越简单越好。我们考虑将减法转换成加法,这就引入了反码。正数的反码还是正数,负数的反码是除符号位外,全部取反。一个二进制数(原码)减去另一个二进制数(原码)就转换为一个二进制数反码加上另一个二进制数的反码。
十进制 | 原码 | 反码 |
---|---|---|
1 | 0001 | 0001 |
-1 | 1000 | 1111 |
二进制结果 | 1000 | |
十进制结果 | -0 |
十进制 | 原码 | 反码 |
---|---|---|
1 | 0001 | 0001 |
-2 | 1010 | 1101 |
二进制结果 | 1110 | |
二进制结果求反 | 1001 | |
十进制结果 | -1 |
如上图所示反码的引入,虽然解决了加法变减法的转换,却引入负零和循环进位等复杂计算逻辑。
补码的出现则进一步简化了运算,补码只需要进行简单加法,舍弃进位即可得到结果。
十进制 | 原码 | 反码 | 补码 |
---|---|---|---|
1 | 0001 | 0001 | 0001 |
-2 | 1010 | 1101 | 1110 |
二进制结果 | 1111 | ||
二进制结果反码 | 1110 | ||
二制结果 | 1001(-1) |
- Integer的边界问题
通过引入补码,计算机的加减运算简化为了简单的相加,数值在计算机内存中也以补码的形式存储。
原码 => 补码: 取反加一(符号位不参加运算)
补码=> 原码: 取反加一(一般情况下,符号位不参加运算)
在补码中,1000不再表示负零,而是-8。所以给定n位存储长度,可以表达数值范围为-2的n次方到2的(n-1)次方。
由补码求其绝对值补码的公式为,补码取反加一(包括符号位)
以下为给定4位长度的二进制数求绝对值。
十进制 | 原码 | 补码 | 其绝对值原码 |
---|---|---|---|
-1 | 1001 | 1111 | 0001 |
-8 | 1000 | 1000 | 1000 |
-2147483648为int 给定32位最小的值,与-8类似,绝对值与其本身一致。
最后总结
在计算机数值计算时,需要考虑数值精度是否一致和是否有溢出。
- 数值超过数据类型支持最大精度溢出后,计算结果出错(不仅仅是丢失进度)
- 数值计算,结果精度就最大的精度
- Integer计算需要考虑是否有缓存