java中的数据类型
1 概述
Java 是一门面向对象的编程语言,其核心原则之一是一切皆对象。然而,基本数据类型(如 int、double、char 等)并非对象,不具备对象的特性,例如不能调用方法、不能参与继承体系等。而包装类(如 Integer、Double、Character 等)作为 Object 的子类,将基本数据类型进行了对象化封装,使得它们能够更好地融入到 Java 面向对象的编程环境中,符合面向对象的设计理念。如下图所示,内置类和对应的包装类:
内置类型(基本数据类型) | 对应的包装类(java.lang 包下) | 说明 |
---|---|---|
byte | Byte | 8 位整数,用于表示范围相对较小的整数值,取值范围是 -128 到 127(包含边界值)。 |
short | Short | 16 位整数,可表示的数值范围比 byte 更大一些,范围是 -32768 到 32767(包含边界值)。 |
int | Integer | 32 位整数,在日常编程中使用频率较高,能表示较广泛的整数值,范围是 -2147483648 到 2147483647(包含边界值)。 |
long | Long | 64 位整数,适用于需要表示更大范围整数的场景,例如处理大文件的字节数、时间戳等,其取值范围极大。 |
float | Float | 32 位浮点数,用于表示单精度的小数,在存储和运算时会存在一定的精度损失,常用于对精度要求不是特别高的浮点数值表示场景。 |
double | Double | 64 位浮点数,双精度浮点数,相比 float 能提供更高的精度,常用于科学计算、金融计算等对精度要求相对较高的领域,但同样存在精度问题。 |
char | Character | 字符类型,用于表示单个字符,在 Java 中采用 Unicode 编码,可以表示世界上几乎所有的字符,例如字母、数字、标点符号以及各种语言的字符等。 |
boolean | Boolean | 布尔类型,只有两个取值,即 true (真)和 false (假),常用于条件判断、逻辑控制等场景,比如在 if 语句、while 语句等控制结构中使用。 |
包装类中通用的方法:
方法分类 | 方法定义(以Xxx 代表具体包装类,T 代表对应基本类型) | 说明 | 适用所有包装类 |
---|---|---|---|
拆箱(基本类型转换) | T xxxValue() | 将包装类对象转换为对应的基本类型(如Integer.intValue() 、Character.charValue() ) | 是 |
装箱(对象创建) | static Xxx valueOf(T value) | 将基本类型转换为包装类对象(可能使用缓存,提高效率) | 是 |
字符串解析为基本类型 | static T parseXxx(String s) | 将字符串解析为对应基本类型(如Integer.parseInt("123") 、Boolean.parseBoolean("true") ) | 除Character 外 |
字符串解析(带进制) | static T parseXxx(String s, int radix) | 按指定进制(2-36)解析字符串(仅整数型包装类支持,如Integer.parseInt("1010", 2) ) | 整数型(Integer /Long /Byte /Short ) |
字符串转换 | static String toString(T value) | 将基本类型转换为字符串(如Integer.toString(123) 、Boolean.toString(true) ) | 是 |
对象转字符串 | String toString() | 重写Object 的toString() ,返回包装类对象的字符串表示 | 是 |
数值比较 | static int compare(T a, T b) | 比较两个基本类型的值,返回-1(a<b)、0(a==b)、1(a>b) | 是 |
常量:最大值 | static final T MAX_VALUE | 表示对应基本类型的最大值(如Integer.MAX_VALUE 、Byte.MAX_VALUE ) | 除Boolean 外 |
常量:最小值 | static final T MIN_VALUE | 表示对应基本类型的最小值(如Long.MIN_VALUE 、Float.MIN_VALUE ) | 除Boolean 外 |
常量:位数 | static final int SIZE | 表示对应基本类型的二进制位数(如Integer.SIZE=32 、Double.SIZE=64 ) | 除Boolean 外 |
对于Character 类型表示的Unicode 编码范围(0到65535),是字符编码的边界值。
2 不同数据类型的取值范围与使用场景
包装类 | 对应基本类型 | 占用内存(位) | 数值范围 | 精度特性 | 典型场景 |
---|---|---|---|---|---|
Byte | byte | 8 | −27-2^7−27 ~ 27−12^7 - 127−1(即 -128 ~ 127) | 整数,无精度损失 | 存储小范围整数(如文件字节、ASCII 字符),节省内存。 |
Short | short | 16 | −215-2^{15}−215 ~ 215−12^{15} - 1215−1(即 -32768 ~ 32767) | 整数,无精度损失 | 存储中等范围整数(如短数组、小范围计数),比 int 更省内存。 |
Integer | int | 32 | −231-2^{31}−231 ~ 231−12^{31} - 1231−1(即 -2147483648 ~ 2147483647) | 整数,无精度损失 | 日常编程中最常用的整数类型(如循环计数、数组索引、普通数值计算)。 |
Long | long | 64 | −263-2^{63}−263 ~ 263−12^{63} - 1263−1 | 整数,无精度损失 | 存储大范围整数(如时间戳、文件大小、大数值计数),避免溢出。 |
Float | float | 32 | 约 ±3.4×1038\pm 3.4 \times 10^{38}±3.4×1038(单精度,有效位数约 6-7 位) | 浮点数,可能有精度损失 | 对精度要求不高的场景(如图形处理、科学计算中的近似值),内存占用比 double 小。 |
Double | double | 64 | 约 ±1.7×10308\pm 1.7 \times 10^{308}±1.7×10308(双精度,有效位数约 15-17 位) | 浮点数,精度高于 float ,但仍可能有损失 | 对精度要求较高的场景(如金融计算、科学实验数据),是默认浮点类型(如 1.2 默认为 double )。 |
3 浮点型存储方式
Java 中浮点型(float
和 double
)遵循 IEEE 754 标准 进行存储,采用科学计数法的二进制形式表示,核心思想是用“符号位 + 指数位 + 尾数位”三部分存储数值,以平衡精度和表示范围。
float
(32位单精度)的存储结构
32位总长度划分为三部分:
位区域 | 位数 | 作用 |
---|---|---|
符号位(S) | 1位 | 0表示正数,1表示负数(仅表示符号,不影响数值大小)。 |
指数位(E) | 8位 | 存储指数的“偏移值”,用于表示数值的数量级(范围:-126 ~ +127)。 |
尾数位(M) | 23位 | 存储二进制小数的有效数字(默认隐含一位整数“1”,实际精度为24位)。 |
示例:存储 0.5f
转换方式为乘二取整法,在IEE754中要求,第一位得为1,所以会将小数点右移并乘10的n次方达到上述目的,次数n位指数,小数部分为位数。
- 二进制为
1.0 × 2⁻¹
- 符号位 S=0(正数)
- 指数 E = -1 + 127(偏移量)= 126 → 二进制
01111110
- 尾数位 M = 0(因
1.0
的小数部分为0) - 最终32位存储:
0 01111110 00000000000000000000000
double
(64位双精度)的存储结构
64位总长度划分为三部分:
位区域 | 位数 | 作用 |
---|---|---|
符号位(S) | 1位 | 同float ,0为正,1为负。 |
指数位(E) | 11位 | 存储指数的“偏移值”,表示范围更大(-1022 ~ +1023)。 |
尾数位(M) | 52位 | 存储二进制小数的有效数字(默认隐含一位整数“1”,实际精度为53位)。 |
示例:存储 0.5
(默认double
)
- 二进制为
1.0 × 2⁻¹
- 符号位 S=0
- 指数 E = -1 + 1023(偏移量)= 1022 → 二进制
01111111110
- 尾数位 M = 0
- 最终64位存储:
0 01111111110 00000000000000000000000000000000000000000000000000000000
float
与 double
的核心区别
类型 | 总位数 | 符号位 | 指数位 | 尾数位 | 指数范围(二进制) | 指数范围(十进制) | 有效精度(二进制) | 有效精度(十进制) | 适用场景 |
---|---|---|---|---|---|---|---|---|---|
float | 32位 | 1位 | 8位 | 23位 | -126 ~ +127 | ±3.4×10³⁸ | 24位(含隐含位) | 6-7位 | 精度要求低、内存敏感场景 |
double | 64位 | 1位 | 11位 | 52位 | -1022 ~ +1023 | ±1.7×10³⁰⁸ | 53位(含隐含位) | 15-17位 | 高精度计算(如金融、科学计算) |
4 精确度问题
有些小数不能精确的转成小数,例如0.1:
以下是将十进制小数 0.1
转换为二进制小数的详细计算步骤,按照乘2取整的方法逐步推导( 乘 2 取整法):
实例
步骤一:初始化
我们要将十进制小数 0.1
转换为二进制,先记录下当前待转换的十进制小数为 0.1
。
步骤二:第一次乘2取整
将当前的十进制小数乘以 2
,即:
此时,取乘积结果的整数部分作为二进制小数小数点后的第一位数字,这里整数部分是 0
,所以二进制小数目前为 0.0
。
然后记录下此次乘积结果的小数部分 0.2
,用于后续继续计算。
步骤三:第二次乘2取整
用上一步得到的小数部分 0.2
乘以 2
,可得:
取这个乘积结果的整数部分作为二进制小数小数点后的第二位数字,整数部分为 0
,此时二进制小数变为 0.00
。
同样,记录下此次乘积结果的小数部分 0.4
用于下一步计算。
步骤四:第三次乘2取整
用新得到的小数部分 0.4
乘以 2
:
取整数部分作为二进制小数小数点后的第三位数字,这里整数部分是 0
,二进制小数更新为 0.000
。
接着记录下小数部分 0.8
继续下一步操作。
步骤五:第四次乘2取整
用小数部分 0.8
乘以 2
:
取整数部分 1
作为二进制小数小数点后的第四位数字,此时二进制小数变为 0.0001
。
记录下此次乘积结果的小数部分 0.6
,用于后续计算。
步骤六:第五次乘2取整
用 0.6
乘以 2
:
取整数部分 1
作为二进制小数小数点后的第五位数字,二进制小数更新为 0.00011
。
再记录下小数部分 0.2
,继续下一步运算。
步骤七:第六次乘2取整
用新出现的小数部分 0.2
乘以 2
:
取整数部分 0
作为二进制小数小数点后的第六位数字,二进制小数变为 0.000110
。
记录下小数部分 0.4
,准备下一轮计算。
步骤八:后续循环
可以发现,从这一步开始,后续出现的小数部分 0.4
又回到了之前步骤三出现过的情况,后续的计算将会不断重复出现 0.4
、0.8
、1.6
、1.2
等这些小数部分以及对应的取整结果,陷入循环。
所以,十进制小数 0.1
转换为二进制小数的结果是 0.00011001100110011...
,这是一个无限循环的二进制小数,通常可以表示为 0.0\dot{0}110
(在二进制下循环节为 0110
)。
如果按照IEEE 754标准将其存储为 float
或 double
类型时,由于尾数位长度有限(float
的尾数位23位,double
的尾数位52位),只能截取前面有限的位数来近似表示这个无限循环的二进制小数,从而导致了精度损失。例如在 float
类型中存储时,实际存储的是这个无限循环二进制小数的近似值,并非精确的 0.1
在二进制下的完整表示。
Double的尾数有52位,而Float仅仅有23位,因此double具有更高的精度。
5 BigInteger和BigDecimal
在 Java 中,BigInteger和BigDecimal是两个用于高精度数值计算的类(你可能想说的是BigDecimal,Java 中并没有BigDouble这个类哦),它们解决了基本数据类型(如int、double等)在处理大数值以及高精度要求场景下的精度不足等问题。
BigInteger
BigInteger 是 Java 中用于表示任意精度整数的类,其存储方式与基本整数类型(如 int、long)有本质区别,核心是通过动态数组存储二进制原码的各位,从而支持任意大小的整数。BigInteger 内部通过一个 int[] 数组(名为 mag,即 “magnitude” 的缩写)存储整数的绝对值的二进制补码表示,并使用一个 int 类型的 signum 标记符号:signum:符号位,取值为 1(正数)、0(零)、-1(负数)。
实例
12345678901234567890
(约10¹⁹)为例,来直观理解 BigInteger
中 mag
数组的存储方式:
步骤1:计算整数的绝对值和二进制表示
- 整数为正数,
signum = 1
(符号为正)。 - 绝对值就是
12345678901234567890
。 - 转换为二进制(简化表示,实际很长):
10101101011110011100110111100111011011101011101011010110100010010
步骤2:按32位拆分二进制位(大端序)
mag
数组的每个元素是32位 int
,按高位在前、低位在后的大端序存储,且不保留前导零:
-
将二进制从左到右(高位到低位)每32位分一组:
- 第一组(最高位32位):
1010110101111001110011011110011
- 第二组(次高位32位):
1011011101011101011010110100010
- 第三组(剩余低位,不足32位补0,但因无需求前导零,实际存储有效位):
0010
(此处仅为示例,实际会补齐32位但去除前导零)。
- 第一组(最高位32位):
-
每组转换为十进制
int
值,存入mag
数组:- 第一组对应
int
值:2874452659
- 第二组对应
int
值:2995155810
- 第三组对应
int
值:2
(简化后)。
- 第一组对应
-
最终
mag
数组为:[2874452659, 2995155810, 2]
BigDecimal
BigDecimal 主要是为了解决浮点数(如 float、double)在运算时因二进制存储格式而产生精度损失的问题。在很多对精度要求严苛的领域,比如金融行业(涉及货币金额计算、利息计算、汇率换算等)、科学计算中需要精确数值的情况等,都需要使用 BigDecimal 来确保计算结果的准确性。
BigDecimal
采用 “整数数组 + 小数点位置 + 符号位” 的三元存储结构,彻底解决了浮点型(float
/double
)的精度损失问题,以下是其存储方式的详细解析:
核心存储结构
BigDecimal
内部通过三个关键部分表示一个十进制数:
BigInteger intVal
:存储数值的"无小数点整数形式"(所有有效数字,不含小数点)。int scale
:表示小数点位置(即小数部分的位数,scale ≥ 0
时为小数位数,scale < 0
时表示整数部分后有-scale
个零)。- 符号位:由
intVal
内部的signum
变量表示(1
为正,-1
为负,0
为零)。
存储细节拆解
以 123.45
和 -67.890
为例,具体说明存储逻辑:
例1:存储 123.45(正数)
-
步骤1:处理数值为无小数点整数
去掉小数点,保留所有有效数字 →12345
。
这部分由intVal
(BigInteger
类型)存储,其内部mag
数组为[12345]
(二进制拆分的整数绝对值)。 -
步骤2:记录小数点位置(
scale
)
原数小数点后有 2 位 →scale = 2
。 -
步骤3:标记符号
正数 →signum = 1
。最终存储:
intVal=12345
+scale=2
+signum=1
→ 还原为12345 / 10² = 123.45
。
例2:存储 -67.890(负数)
-
步骤1:处理数值为无小数点整数
去掉小数点和负号 →67890
(注意:末尾的 0 是有效数字,会保留)。
intVal
的mag
数组为[67890]
。 -
步骤2:记录小数点位置(
scale
)
原数小数点后有 3 位 →scale = 3
。 -
步骤3:标记符号
负数 →signum = -1
。最终存储:
intVal=67890
+scale=3
+signum=-1
→ 还原为-67890 / 10³ = -67.890
。
特殊场景处理
-
整数(如 123)
scale = 0
(无小数部分),存储为intVal=123
+scale=0
→123 / 10⁰ = 123
。 -
纯小数(如 0.0045)
无小数点整数为45
,scale = 4
→45 / 10⁴ = 0.0045
。 -
科学计数法(如 1.23e+5 = 123000)
等价于123000
,scale = -2
(scale < 0
表示整数部分后补 2 个零) →123 × 10² = 123000
。
总结
BigDecimal
通过 “剥离小数点保留全部有效数字(intVal
)+ 记录小数点位置(scale
)+ 单独标记符号” 的设计,实现了十进制数的完全精确存储。这种方式避开了二进制与十进制转换的天然矛盾,是高精度场景(如货币计算)的必备工具。
其核心思想可概括为:“用整数存储所有有效数字,用 scale 记住小数点在哪,用 signum 标记正负”。
有效数字的存储和BigInteger 一致也就是,BigDecimal可以表示任意精度的数字。