概要
这一章的复习主要涵盖 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的二进制位来表示选和不选的状态