概要
这一章的复习主要涵盖 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 | 123 => int |
- 直接初始化为
float
时,需要指定为f / F
作为后缀 - 直接初始化
long
且范围超过了int
,需要使用l \ L
作为后缀
进制
格式 | 进制 |
---|---|
0b10 | 二进制 |
100 | 十进制(默认) |
0144 | 八进制 |
0x64 | 十六进制 |
精度
1 | 低 ------------------------------------> 高 |
类型转化
隐式类型转化
自动类型转换的实现需要同时满足两个条件:
- 两种数据类型彼此兼容
- 目标类型的取值范围大于源数据类型 (低级类型数据转换成高级类型数据)
如 byte
类型向 short
类型转换时,由于 short
类型的取值范围较大,会自动将 byte
转换为 short
类型
数值型数据的转换:
1 | byte-> short-> int-> long-> float-> double |
字符型转换为整型:
1 | char-> int |
隐式类型转化问题:
进行算数运算过程中会自动提升类型
- 返回结果为算式中的 最高精度的类型,如果存在字符串即为
String
类型 - 转换时机为 根据结合律运算顺序!第一次碰到高精度 类型的时候
1 | int i = 3; |
*
的结合律要比 +
的集合率高,所以在 ret2
中先执行了 i / 2
后才遇到 f
向上转型为 float
。
显示类型转化
(type)variableName
使用 ( )
操作符可以对类型进行强制类型转化,在 高精度类型转向低精度类型 的过程会出现 精度丢失 的情况
浮点值 转成 整形值 的过程中会直接进行 截尾
1 | (int)12.2 == 12 ///:~ true |
包装类型
所有的包装类型都 extends
自 java.lang.Number
,这些包装类型中,都是用了 private final
修饰来保存对应的基本数据类型的值,保证无法进行修改。
Integer
方法从 new Integer(int i)
jdk 9
就被废弃了,推荐使用 Integer # valueOf
方法来新建对象
1 | Integer a = 123; |
缓冲池
Type | Range |
---|---|
Boolean | true ~ false |
Byte | 0 ~ 1 |
Short | -128 ~ 127 |
Integer | -128 ~ 127 |
Character | \u0000 ~ \u007F |
给定区间中会有无数个 float
和 double
,所以这两种类型的包装类型不设置缓存池
Integer.IntegerCache
缓冲池
IntegerCache
规定的缓存范围 IntegerCache.low ~ IntegerCache.high
为 -128 ~ 127
,如果使用 Integer # valueOf
来新建 Integer
对象的时候,会直接从池中返回引用,因此,介于这个范围的 Integer
对象
但是通过 new Integer(int value)
来新建对象,不会调用缓冲区,所以就算是缓存区范围内的 int
,创建的所有的对象都不是相同的。
1 | public final class Integer extends Number |
缓存池对比较的影响:
后面的装箱有提到
IntegerCache
缓存池,实现了 享元模式,因此使用Integer # valueOf
来创建对象的时候,命中缓存池的时候,得到的是同一个对象的引用,
所以a == b: true
但是
new Integer(int)
创建对象不通过缓存池,所以即使在缓存池的范围值,e == f: false
1 | private static void integerCache() { |
高精度
除了上面所罗列的包装类型之外,java
中还有这高精度数字:
BigInteger
: 高精度整数BigDecimal
: 高精度小数
高精度的运算和比较大小使用其内部定义的函数,不能直接使用运算符!
1 | BigDecimal a = new BigDecimal("2.00"); |
自动装箱机制
自动装箱知识点
- 自动装箱可以自动完成 基本数据类型 和 包装类型 之间转换
- 自动装箱机制 不能用于数组
装箱实现
这里以 Integer
为例,探究 Integer
的自动装箱机制。
1 |
|
两个核心方法:
- 装箱
int -> Integer
:Integer valueOf(int i)
- 拆箱
Integer -> int
:int intValue()
- 在使用将
int
基本类型的值赋值给Integer
包装类型的过程中,会调用Integer
源码中中的int intValue()
方法,进行类型转化 - 同理从
Integer
到int
就是调用Integer valueOf(int i)
的逆向类型转化
Integer
中使用了 享元模式,对于 -128 ~ 127
之间的值已经在累加器加载 Integer
类的时候,将这区间的数值初始化到了 Integer[] cache
缓存池中,如果 IntegerCache.low <= x && x <= IntegerCache.high
介于这个区间之中,调用这区间的 Integer.vauleOf(x)
并不会 new
新的 Integer
对象。
1 | // java 12 source |
坑和经验
int
记20
亿,十进制表示的时候就是1e9
=>1
后面9
个0
,做算法题的时候根据这个可以粗略的判断一下范围,选择是否使用比int
范围更大的数据类型在
int compare(int b)
比较函数中,经常使用return a - b;
来实现,然而这样可能会爆掉int
,好的方法还是通过 比较来实现int
用来记录DFS
的状态是一种很好的技巧,因为DFS
的时间复杂度一般来说是O(n!)
指数级别的,状态位数不是很多可以使用int
的二进制位来表示选和不选的状态