入门篇-其之三-基本数据类型及其转换
本文最后更新于:2025年4月6日
Java的数据类型分为基本数据类型和引用数据类型,具体分类如下图:
对于初学者而言,认为字符串类型String
也属于基本数据类型,事实上String
属于类,即引用数据类型。从String
的源码中,我们就可以看出其使用的class
关键字进行修饰:
观察上述结构图我们可以发现Java的八种基本类型又可以细分成四类:整数类型、浮点类型、字符类型和布尔类型。本文将会对这四大类型一一进行讲解。
一、整数类型
整数类型,简称整型。Java中存储整型由四个类型组成:**byte
、short
、int
、long
**。其中int
类型最常用。这四个数据类型的对照表如下所示:
计算机存储大小 | 存储范围(使用数学开闭区间表示) | 默认值 | |
---|---|---|---|
byte |
8位,1字节 | [-128, 127] | 0 |
short |
16位,2字节 | [-216-1,216-1-1] | 0 |
int (默认) |
32位,4字节 | [-232-1,232-1-1] | 0 |
long |
64位,8字节 | [-264-1,264-1-1] | 0L |
在定义这四个类型的变量时,需要注意定义的整数不要超过其存储范围(尤其是byte
类型,因为它的存储范围最小)。
例如:我想定义一个byte
、short
、int
类型的变量并赋值,其代码如下:
1 |
|
运行结果:
在使用long
类型的时候需要注意:**long
类型的数据后面需要加上L
或l
**(不推荐小写l
,因为小写l
很有可能和数字1或者大写字母I混淆)。示例代码如下:
1 |
|
运行结果:
细心的小伙伴会发现,为什么变量num1
的值88
后面没有加上后缀L
呢?
由于整型的默认使用的int
类型,而long
类型的范围比int
大,因此数字88
会由int
类型自动提升为long
类型,这种现象称作自动类型提升(本文后面会讲到自动类型提升)。因此long num1 = 88;
并不会报语法错误。
而变量num2
的值6666666666666666666
已经超出了int
类型的最大范围,但是这个数字在long
范围内,此时就必须要加上后缀L
。
以下是对定义long
类型变量的总结说明:
说明 | 案例 |
---|---|
在int 范围内的数字,可以用L 或l 表示long 类型,也可以不使用后缀。 |
long num1 = 32; long num2 = 43L; |
如果表示的数字在int 范围之外,但是在long 的范围之内,则必须使用L 或l 作为后缀。 |
long num = 66666666666666L; |
如果你并不能确定所定义的整数是否在int
范围,我个人的建议就是**只要定义long
类型的整数,就在数字后面加个后缀L
**。
二、浮点类型
浮点类型,其实就是我们说的小数类型。浮点类型主要由float
和double
类型组成。其中,**float
类型的数值后必须要加f
或F
为后缀**,二者对照表如下所示:
计算机存储位数 | 存储范围(使用数学开闭区间表示) | 数字后缀 | 默认值 | 精度 | |
---|---|---|---|---|---|
float |
32位,4字节 | [-2128,2128] | f 或F (必须写后缀) |
0.0f或0.0F | 7位小数 |
double |
64位,8字节 | [-21024,21024] | d 或D (非强制要求,一般不写后缀) |
0.0 | 15位小数 |
示例代码如下:
1 |
|
运行结果为:
为什么d2
的输出结果是8.0?由于8默认为int
类型,给变量d2
赋值时,int
类型的数值会向范围更大的double
转换(自动类型提升,在后面文章会讲到),而double
是浮点类型,后面需要跟随小数点,默认会在后面加上.0
(一位小数),即输出结果为8.0。同理,f3
的输出结果为6.0。
从输出结果中我们还能看出,10 / 3
得到的是无限循环小数,但是float
类型变量f4
输出结果保留了7位小数,而double
类型变量d3
输出结果保留了15位小数。由这两个输出结果可以印证两个浮点类型的精度大小。
在日常使用过程中,使用double
的次数要比float
多,个人总结有如下三点:
float
类型数值需要在必须其后面加上f
和F
,而double
不需要在值后面加后缀符。double
存储范围比float
的大,并且浮点类型数值默认类型就是double
。double
的精度要比float
的高,表示的数值更加准确。
三、字符类型
字符类型,即char
类型,用来存储单个字符,使用单引号和单个字符表示,因此在单引号中写多个字符是错误写法:
1 |
|
char
是一个单一的16位的Unicode字符,它的存储范围是[0,65535]
,即'\u0000'
到'\uffff'
。
这里会有小伙伴问:char
不是只能表示单个字符吗?这就要说到Unicode字符表了,这个表存储了所有的字符(各种符号、中文英文等各种字符),Unicode字符表中的每个字符默认使用的是以\u
和十六进制数组合表示,也就是说\u0000
就是一个Unicode值,这个Unicode值对应着字符表中的一个字符。
Unicode字符表中存储了所有的可用的字符,\u0000
其实表示的时候Unicode字符表中第一个字符,编写测试代码如下:
1 |
|
运行结果如下:
代码中我写了三个输出语句,其中第一个直接输出这个字符,但是从运行结果中我们发现这个语句确实输出了,但是控制台无法显示这个字符。
为了进一步验证输出的字符是否是Unicode字符表第一个字符,这里我使用了一个if
判断。如果我们定义变量和\u0000
相等时,输出ch1是Unicode字符表中的第一个字符
,此时也就说明了第一个字符确实在计算机中存在,只是无法正常显示;相反,\u0000
并不是Unicode字符表中的第一个字符。运行结果正如我们所料,输出的内容是ch1是Unicode字符表中的第一个字符。
四、布尔类型
boolean
类型,即布尔类型,它只有两个值:true
(真)和false
(假)。通常用于条件表达式的判断(条件表达式后续文章会讲到),例如:我们都知道20 > 30是假,即判断结果为false
。
1 |
|
运行结果:
五、数字的进制表示(了解)
在中学期间我们学过数字有二进制、八进制、十进制和十六进制。
- 二进制数字是由0、1组成,满二进一。
- 八进制数字是由0~7组成,满八进一。
- 十六进制是由0~9、A、B、C、D、E、F组成,满十六进一
日常我们表示数字都是采用十进制,Java程序表示数字亦是如此。那么,如何表示二进制、八进制、十六进制的数字?
以十进制的数字22
为例,转换为各个进制的数字如下:
二进制 | 八进制 | 十六进制 |
---|---|---|
10110 | 26 | 16 |
在Java中,表示二进制数字,需要在数字前面加上0B
或0b
;如果表示八进制数字,需要在数字前面加上0
即可;如果是十六进制的数字,需要在数字前面加上0X
或者0x
,以下是示例代码:
1 |
|
运行结果:
从运行结果我们可以看出:输出的数字无论是哪一种进制,默认都会转换为十进制的数字22
。
如果我想直接将十进制数字22
转换为各个进制并进行输出。
例如:我想定义的变量是int
类型,可以使用int
的包装类Integer
,在Integer
类中有和进制转换相关的方法:
toBinaryString(num)
:将十进制数字转换为二进制数字并表示。toOctalString(num)
:将十进制数字转换为八进制数字并表示。toHexString(num)
:将十进制数字转换为十六进制数字并表示。
1 |
|
运行结果:
六、原码、反码、补码(了解)
原码、反码、补码是计算机中表示数值的一种方式,主要应用于计算机的加减运算。
原码是最基本的表示方法, 直接将数值以二进制的形式表示,原码就是符号位加上真值的绝对值,即第一位表示正负号(0为整数,1为负数),其他位表示值。
例如:127
的原码是01111111
,-127
的原码是11111111
。
原码的优点就是直观,容易理解。
反码:正数的反码就是其原码本身,负数的反码在其原码的基础上保持符号位不变,其他位取反。
例如:-127
的反码是10000000
,127
的反码是01111111
。
补码:正数的补码就是其原码本身,负数的补码需要在反码的基础上加1。
例如:-127
的补码就是10000001
。
想深入了解此方面的内容的小伙伴,详见这篇文章:《原码、反码、补码的基本概念》,我个人觉得写的很棒!
七、自动类型提升
前面我们已经讲过了8种基本数据类型,按照数据存储范围来比较:double > float > long > int > short 、char > byte
自动类型提升是指小范围的数据类型向大范围的数据类型进行转换。
boolean
类型不能进行自动类型提升或强制类型转换。
例如:short
的存储范围比int
小,因此,short
类型的值赋值给int
类型的变量时,short
类型的值自动转换为int
类型,代码如下:
1 |
|
运行结果:
上述案例可以看出,s1
赋值给i1
的时候并没有报错,原因就在于s1
自动转换为int
类型的值赋给i1
。
自动类型提升可能存在的特殊情况:
情况一:当byte
、short
、char
三者互相参与运算时,默认转为int
类型。
1 |
|
当我们解除一个错误写法的注释(例如byte num3 = num1 + num2;
)。我们可以执行javac
命令查询错误信息:
情况二:整数类型向浮点类型转换时,默认后面会带.0
。
1 |
|
运行结果:
情况三:**char
类型向更高数据范围(例如:int
、long
等)转换时,以数字的形式输出。**
1 |
|
八、强制类型转换
和自动类型提升相比,强制类型转换正好相反,由大范围的数据类型向小范围的数据类型进行转换,转换格式如下:
1 |
|
如果我想将long
类型的数据转换为byte
、short
、int
类型的数据,由于long
是大范围的数据类型,向这三个小范围数据类型转换时需要进行强制类型转换。以下是示例代码:
1 |
|
运行结果:
当然,强制类型转换也会存在如下的情况:
情况一:浮点类型转换成整数类型时,会出现精度损失,即小数点会被截断(不会四舍五入),只保留整数部分。
1 |
|
运行结果:
情况二:要转换的数字超出目标类型的范围,Java会自动对整数进行溢出处理,不会得到预期的值。
例如:定义一个int
类型的变量130
,将其转换成byte
类型,而byte
类型的存储范围是[-128,127]
,130
很明显超出了这个范围,强制转换的结果不会符合我们的预期,示例代码如下:
1 |
|
很明显,输出结果并不符合我们的预期,而是得到了值-126
,接下来我们从底层角度进行分析:
由于int
为4字节32位,每一位是由二进制的0和1表示,因此130转换成二进制数(32位)为:
int
类型强制转换成byte
类型以后,只保留后八位,结果如下:
得到的10000010
是源码,8位的byte
第一位是符号位,0表示正号,1表示负号。很明显这个数是负数,表示负数需要先将原码转换成反码,反码变成补码,补码再转换成十进制数字以后就是byte
类型的结果。首先我们先将其转换成反码(符号位除外):
将反码加1之后,就得到补码:
将11111110
转换成十进制数为(第1位是符号位,是负数):
$$
-(1\times2^{6}+1\times2^{5}+1\times2^{4}+1\times2^{3}+1\times2^{2}+1\times2^{1}+0\times2^{0})=-126
$$
因此强制类型转换得到的结果是-126
。
情况三:**byte
、short
、char
进行运算时,会被提升为int
类型,然后再进行计算**。要想转换成小范围数据类型,需要进行强制类型转换。
以下写法无法通过编译而报错:
1 |
|
无法通过编译,因为进行加减法运算时,变量会自动提升为int
类型,得到的结果也是int
类型,和左侧原有的数据类型不匹配而报错:
正确的写法是:将得到的结果进行强制类型转换:
1 |
|
运行结果符合预期: