[java复习] 数据类型

概要

这一章的复习主要涵盖 java 所有的 基本数据类型包装类型 的源码分析,IntegerCache 缓存池源码分析 以及这两者之间转化所用的 自动装箱机制 实现原理。
这里整理的都是个人觉得比较重要重新认真分析的东西,不一定全部为基础,如果有不准确的地方,还望大佬从评论区指出,感激不尽。

文章中的所有的自定义的测试都在自己的 java-review 仓库中,本文对应的链接:

常见数据类型

数据类型 字节数 大小/位 包装类型 默认值 可表示数据范围
byte 1 8 Byte (byte)0 -128 ~ 127
boolean 1 8 Boolean false true ~ false
short 2 16 Short (short)0 -32768 ~ 32767
char 2 16 Character ‘\u0000’(null) 0 ~ 65535
int 4 32 Integer 0 -2147483648 ~ 2147483647
float 4 32 Float 0.0 1.4E-45 ~ 3.4028235E38
long 8 64 Long 0L -9223372036854775808 ~ 9223372036854775807
double 8 64 Double 0.0 4.9E-324 ~ 1.7976931348623157E308
  • 需要注意的一点就是 java 是没有无符号类型的。

基本类型

为什么java中存在基本类型

c++ 相似,java 中创建基本类型不需要调用 new 关键字,而是使用一个 并非引用的自动变量,直接保存在 中,效率更高

java 是一门 面向对象 的语言,而像 int 之类的基本类型不能作为对象,为此就为这些基本类型都创建了 对应的包装类型

默认类型

  • 整数,默认为 int
  • 非整数,默认为 double
1
2
123 => int
1.1 => double

  • 直接初始化为 float 时,需要指定为 f / F 作为后缀
  • 直接初始化 long 且范围超过了 int,需要使用 l \ L 作为后缀

进制

格式 进制
0b10 二进制
100 十进制(默认)
0144 八进制
0x64 十六进制

精度

1
2
3
低  ------------------------------------>  高

byte, short, char—> int —> long—> float —> double

类型转化

隐式类型转化

自动类型转换的实现需要同时满足两个条件:

  1. 两种数据类型彼此兼容
  2. 目标类型的取值范围大于源数据类型 (低级类型数据转换成高级类型数据)

byte 类型向 short 类型转换时,由于 short 类型的取值范围较大,会自动将 byte 转换为 short 类型

数值型数据的转换:

1
byte-> short-> int-> long-> float-> double

字符型转换为整型:

1
char-> int

隐式类型转化问题:

进行算数运算过程中会自动提升类型

  1. 返回结果为算式中的 最高精度的类型,如果存在字符串即为 String 类型
  2. 转换时机为 根据结合律运算顺序!第一次碰到高精度 类型的时候
1
2
3
4
5
6
7
8
9
10
11
12
int i = 3;
float f = 1.0f;

double ret1 = f * i / 2 ;
double ret2 = f + i / 2 ;
System.out.println("ret1: " + ret1);
System.out.println("ret2: " + ret2);

/*///:~
ret1: 1.5
ret2: 2.0
*/

* 的结合律要比 + 的集合率高,所以在 ret2 中先执行了 i / 2 后才遇到 f 向上转型为 float

显示类型转化

(type)variableName 使用 ( ) 操作符可以对类型进行强制类型转化,在 高精度类型转向低精度类型 的过程会出现 精度丢失 的情况

浮点值 转成 整形值 的过程中会直接进行 截尾

1
2
(int)12.2 == 12  ///:~  true
(int)-0.6 == 0 ///:~ true

包装类型

所有的包装类型都 extendsjava.lang.Number ,这些包装类型中,都是用了 private final 修饰来保存对应的基本数据类型的值,保证无法进行修改。

Integer

new Integer(int i) 方法从 jdk 9 就被废弃了,推荐使用 Integer # valueOf 方法来新建对象

1
2
3
4
5
6
Integer a = 123;
Integer b = 123;

System.out.println("a == b: " + a == b);

//~ a == b: true

缓冲池

Type Range
Boolean true ~ false
Byte 0 ~ 1
Short -128 ~ 127
Integer -128 ~ 127
Character \u0000 ~ \u007F

给定区间中会有无数个 floatdouble,所以这两种类型的包装类型不设置缓存池

Integer.IntegerCache 缓冲池

IntegerCache 规定的缓存范围 IntegerCache.low ~ IntegerCache.high-128 ~ 127,如果使用 Integer # valueOf 来新建 Integer 对象的时候,会直接从池中返回引用,因此,介于这个范围的 Integer 对象

但是通过 new Integer(int value) 来新建对象,不会调用缓冲区,所以就算是缓存区范围内的 int,创建的所有的对象都不是相同的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public final class Integer extends Number
implements Comparable<Integer>, Constable, ConstantDesc {

/* new Integer(int value) 不经过缓存池,创建的总是新的对象 */
@Deprecated(since="9")
public Integer(int value) {
this.value = value;
}

@HotSpotIntrinsicCandidate
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 final Integer[] cache;
static Integer[] archivedCache;

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
h = Math.max(parseInt(integerCacheHighPropValue), 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;

// Load IntegerCache.archivedCache from archive, if possible
VM.initializeFromArchive(IntegerCache.class);
int size = (high - low) + 1;

// Use the archived cache if it exists and is large enough
if (archivedCache == null || size > archivedCache.length) {
Integer[] c = new Integer[size];
int j = low;
for(int i = 0; i < c.length; i++) {
c[i] = new Integer(j++);
}
archivedCache = c;
}
cache = archivedCache;
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}

private IntegerCache() {}
}

}

缓存池对比较的影响

  • 后面的装箱有提到 IntegerCache 缓存池,实现了 享元模式,因此使用 Integer # valueOf 来创建对象的时候,命中缓存池的时候,得到的是同一个对象的引用,
    所以 a == b: true

  • 但是 new Integer(int) 创建对象不通过缓存池,所以即使在缓存池的范围值, e == f: false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static void integerCache() {
Integer a = Integer.valueOf(200); // 没有命中缓存池
Integer b = Integer.valueOf(200);

Integer c = Integer.valueOf(120); // 命中缓存池
Integer d = Integer.valueOf(120);

System.out.println("a == b: " + (a == b));
System.out.println("c == d: " + (c == d));

System.out.println("a == b: " + (a == b));
System.out.println("c == d: " + (c == d));
System.out.println("e == f: " + (e == f));

/*///:~

a == b: false
c == d: true
e == f: false
*/
}

高精度

除了上面所罗列的包装类型之外,java 中还有这高精度数字:

  1. BigInteger: 高精度整数
  2. BigDecimal: 高精度小数

高精度的运算和比较大小使用其内部定义的函数不能直接使用运算符!

1
2
3
4
5
6
BigDecimal a = new BigDecimal("2.00");
BigDecimal b = new BigDecimal("2.0");

//! print(a.equals(b)); // false

int result = a.compareTo(b);

自动装箱机制

自动装箱知识点

  1. 自动装箱可以自动完成 基本数据类型包装类型 之间转换
  2. 自动装箱机制 不能用于数组

装箱实现

这里以 Integer 为例,探究 Integer 的自动装箱机制。


1
2
3

Integer integer = 1; // call int intValue()
int i = integer; // call Integer valueOf(int i)

两个核心方法

  1. 装箱 int -> Integer : Integer valueOf(int i)
  2. 拆箱 Integer -> int : int intValue()
  • 在使用将 int 基本类型的值赋值给 Integer 包装类型的过程中,会调用 Integer 源码中中的 int intValue() 方法,进行类型转化
  • 同理从 Integerint 就是调用 Integer valueOf(int i) 的逆向类型转化

Integer 中使用了 享元模式,对于 -128 ~ 127 之间的值已经在累加器加载 Integer 类的时候,将这区间的数值初始化到了 Integer[] cache 缓存池中,如果 IntegerCache.low <= x && x <= IntegerCache.high 介于这个区间之中,调用这区间的 Integer.vauleOf(x) 并不会 new 新的 Integer 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// java 12 source

public final class Integer extends Number
implements Comparable<Integer>, Constable, ConstantDesc {

private final int value;

@HotSpotIntrinsicCandidate
public int intValue() {
return value;
}

@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high) // 命中缓存池,直接返回池中对应对象
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

}

坑和经验

  • int20 亿,十进制表示的时候就是 1e9 => 1 后面 90,做算法题的时候根据这个可以粗略的判断一下范围,选择是否使用比 int 范围更大的数据类型

  • int compare(int b) 比较函数中,经常使用 return a - b; 来实现,然而这样可能会爆掉 int,好的方法还是通过 比较来实现

  • int 用来记录 DFS 的状态是一种很好的技巧,因为 DFS 的时间复杂度一般来说是 O(n!) 指数级别的,状态位数不是很多可以使用 int 的二进制位来表示选和不选的状态