类型 | 大小 bit | 范围 | 默认值 | |
---|---|---|---|---|
byte | 8 | -128 ~ 127 | 0 | |
short | 16 | -32768 ~ 32767 | 0 | |
int | 32 | -2,147,483,648 ~ 2,147,483,647 | 0 | |
long | 64 | - 2^63 ~ 2^63 - 1 | 0L | |
float | 32 | 1.4E-45 ~ 3.4028235E38 | 0.0f | |
double | 64 | 4.9E-324 ~ 1.7976931348623157E308 | 0.0d | |
boolean | 8 | true, false | false | |
char | 16 位 Unicode 字符 | 0 ~ 65535 | \u0000 |
问题 1
byte a = 127;
a += 1;
System.out.println(a);
为了弄清楚这个问题,我们先来了解一下 每个数据所能表示的大小以及范围是怎么来的,以 byte 为例
0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|
符号位 0 正 1 负 | 值 | 值 | 值 | 值 | 值 | 值 | 值 |
上面的内存形式表现了一个字节所能存储的最大的正数,
1
∗
2
0
+
1
∗
2
1
+
1
∗
2
2
+
.
.
.
+
2
6
=
∑
n
=
0
6
a
n
=
a
1
(
1
−
q
n
)
1
−
q
=
1
∗
(
1
−
2
7
)
1
−
2
=
2
7
−
1
=
127
1 * 2^0 + 1 * 2^1 + 1 * 2^2 + ... + 2^6 = \sum_{n=0}^{6}{a^n} = \frac {a_1(1 - q^n)} {1- q} = \frac {1 * (1 - 2^7)}{ 1- 2} = 2^7 - 1 = 127
1∗20+1∗21+1∗22+...+26=n=0∑6an=1−qa1(1−qn)=1−21∗(1−27)=27−1=127
按照上面的方式,我们计算一下最小值,只需要把符号位变成1就可以了
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|
符号位 0 正 1 负 | 值 | 值 | 值 | 值 | 值 | 值 | 值 |
计算结果为 -127, 不对啊, 哪出问题了?
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
---|---|---|---|---|---|---|---|
符号位 0 正 1 负 | 值 | 值 | 值 | 值 | 值 | 值 | 值 |
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
---|---|---|---|---|---|---|---|
符号位 0 正 1 负 | 值 | 值 | 值 | 值 | 值 | 值 | 值 |
当处于这两个状态的时候,我们可以理解为 +0 和 -0,是不是有点重复了, 把负数的范围缩小1,就能够避免两个 0的出现了。 -0 - 1 ~ -127 - 1 = -1 ~ -128
把原来的 0 ~ 127 和 -0 ~ -127 改成 0 ~ 127 和 -1 ~ -128 更合理,那么最终的范围就变成 - 128 ~ 127 了
但-128在内存中该怎么表示呢?
我们以最开始的那个题的答案就是-128,那我们就以它们在内存中的形式来讨论这个-128是怎么来的。
首先我们要清楚一点,计算机中的数的数据都是以补码的形式存储的,至于为什么用补码,那就是更方便,更容易易计算。
正数的 原码, 补码, 反码都是一样的, 如 1
原码 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
---|---|---|---|---|---|---|---|---|
反码 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
补码 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
负数的补码是在原码的基础是除符号位外,所有的位取反。 补码则是在反码的基础上末位加1. -1的补码如下:
原码 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
---|---|---|---|---|---|---|---|---|
反码 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 |
补码 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
1 - 1 我们可以理理解成 1 + (-1), 结果就是 0 0 0 0 0 0 0 0, 那就是 0了。
原码 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|---|
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
127 + 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
内存中结果的补码的形式就是 1 0 0 0 0 0 0 0, 还原成源码是 1 0 0 0 0 0 0 0. 所以我们可以认为机器会把 -0 解析成该范围表示的一个最小数,如这里就是 -2^7.如果是16位那么就是 10000000 00000000,那所表示的就是 -2^15了。
按照上面的计算,我们可以再看一下 127 + 2, 结果 应该就是 -127
127 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
---|---|---|---|---|---|---|---|---|
2 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
127 + 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
还原成 反码 为 1 0 0 0 0 0 0 0 , 原码为 1 1 1 1 1 1 1 1 , 那值就是 -127 了。
问题2
Integer a = 40;
Integer b = 40;
System.out.println(a == b); true
两个对象的指针居然是一个, why?
在我们的8种基本类型的包装类中,除了float和double以外,都实现了常量池,如果取的值在[-128, 127]中,那么这些值就会从常量池中取出,不会再生成一个新的对象
//Integer 缓存代码 :
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);
}
当我们用 Integer a = 40;这种形式去生成一个Integer对象时,实际上是会调用 Interger 的valueOf 方法。
IntegerCache.high 应该等于 127, IntegerCache.low = -128,
IntegerCache.cache 包含的size为256, 下标为 0 ~ 255 的一个数组, 值为[-128, -127, -126, -125 … 127]。
所以上面的问题,其实两次拿到的都是 IntegerCache.cache[40+ (-IntegerCache.low)]的值,所以是同一个对象。