一、基本数据类型
Java 中有 8 种基本数据类型,分别为:
- 6 种数字类型:
- 4 种整数型:
byte
、short
、int
、long
- 2 种浮点型:
float
、double
- 4 种整数型:
- 1 种字符类型:
char
- 1 种布尔型:
boolean
。
这 8 种基本数据类型的默认值以及所占空间的大小如下:
基本类型 | 位数 | 字节 | 默认值 | 取值范围 |
---|---|---|---|---|
byte |
8 | 1 | 0 | -128 ~ 127 |
short |
16 | 2 | 0 | -32768(-2^15) ~ 32767(2^15 - 1) |
int |
32 | 4 | 0 | -2147483648 ~ 2147483647 |
long |
64 | 8 | 0L | -9223372036854775808(-2^63) ~ 9223372036854775807(2^63 -1) |
char |
16 | 2 | ‘u0000’ | 0 ~ 65535(2^16 - 1) |
float |
32 | 4 | 0f | 1.4E-45 ~ 3.4028235E38 |
double |
64 | 8 | 0d | 4.9E-324 ~ 1.7976931348623157E308 |
boolean |
1 | false | true、false |
为什么负值的范围比正值多一
以 int 为例
0000 0000 ~0111 1111 表示 +0 ~ +127
1111 1111 ~1000 0001 表示 -1 ~ -127
-128的表示
咦,我们好像漏了 1000 0000 这个二进制了,哈哈,到现在为止一切都解决了,其实我们人为规定 1000 0000表示十进制中的-128!当然我们不可能说通过 1000 0000这种二进制求其原码得到某一个数,因为非高位全是0,无法再借位减1了!
其实这种规定是很自然的,1000 0000 -0000 0001=0111 1111,而0111 1111 这个补码表示的是+127,我们可以将 1000 0000这个二进制作为正数和负数之间的一个分水岭(尽管1000 0000本身代表负数),1000 0000再进位就会进入负数范围,若借位就会进入正数范围,就好似1000 0000(-128)将 1000 0001(-127) ~0111 1111(+127)首尾连接了起来形成了一个闭环!!!
原码里面这个是负0,补码里面按照定义他就是-128
码长八位时,各码的范围如下。
原码:-127 ~ +127。
反码:-127 ~ +127。
补码:-128 ~ +127。
-128,没有原码反码,只有补码。
二、包装类型
基本类型和包装类型的区别?
用途:除了定义一些常量和局部变量之外,我们在其他地方比如方法参数、对象属性中很少会使用基本类型来定义变量。并且,包装类型可用于泛型,而基本类型不可以。
存储方式:基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被
static
修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。占用空间:相比于包装类型(对象类型), 基本数据类型占用的空间往往非常小。
默认值:成员变量包装类型不赋值就是
null
,而基本类型有默认值且不是null
。比较方式:对于基本数据类型来说,
==
比较的是值。对于包装数据类型来说,==
比较的是对象的内存地址。所有整型包装类对象之间值的比较,全部使用equals()
方法。
为什么说是几乎所有对象实例都存在于堆中呢? 这是因为 HotSpot 虚拟机引入了 JIT 优化之后,会对对象进行逃逸分析,如果发现某一个对象并没有逃逸到方法外部,那么就可能通过标量替换来实现栈上分配,而避免堆上分配内存
⚠️ 注意:基本数据类型存放在栈中是一个常见的误区! 基本数据类型的存储位置取决于它们的作用域和声明方式。如果它们是局部变量,那么它们会存放在栈中;如果它们是成员变量,那么它们会存放在堆/方法区/元空间中。
缓存机制
Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。
Byte
,Short
,Integer
,Long
这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character
创建了数值在 [0,127] 范围的缓存数据,Boolean
直接返回 True
or False
。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
private static class IntegerCache {
static final int low = -128;
static final int high;
static {
// high value may be configured by property
int h = 127;
}
}
如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。
两种浮点数类型的包装类 Float
,Double
并没有实现缓存机制。
Integer i1 = 33;
Integer i2 = 33;
System.out.println(i1 == i2);// 输出 true
Integer i1 = 128;
Integer i2 = 128;
System.out.println(i1 == i2);// 输出 false
Float i11 = 333f;
Float i22 = 333f;
System.out.println(i11 == i22);// 输出 false
Double i3 = 1.2;
Double i4 = 1.2;
System.out.println(i3 == i4);// 输出 false
Integer i1 = 40;
Integer i2 = new Integer(40);
System.out.println(i1==i2);// 输出 false
Integer i1=40
这一行代码会发生装箱,也就是说这行代码等价于 Integer i1=Integer.valueOf(40)
。因此,i1
直接使用的是缓存中的对象。而Integer i2 = new Integer(40)
会直接创建新的对象。
自动装箱拆箱
Integer i = 10
等价于Integer i = Integer.valueOf(10)
int n = i
等价于int n = i.intValue()
;
注意:如果频繁拆装箱的话,也会严重影响系统的性能。我们应该尽量避免不必要的拆装箱操作。