Java的数据类型分为基本数据类型和引用数据类型,具体分类如下图:
对于初学者而言,认为字符串类型String
也属于基本数据类型,事实上String
属于类,即引用数据类型。从String
的源码中,我们就可以看出其使用的class
关键字进行修饰:
观察上述结构图我们可以发现Java的八种基本类型又可以细分成四类:整数类型、浮点类型、字符类型和布尔类型。本文将会对这四大类型一一进行讲解。
整数类型
整数类型,简称整型。Java中存储整型由四个类型组成:byte
、short
、int
、long
。其中int
类型最常用。这四个数据类型的对照表如下所示:
计算机存储大小 | 存储范围(使用数学开闭区间表示) | 默认值 | |
---|---|---|---|
byte |
8位,1字节 | [-128, 127] | 0 |
short |
16位,2字节 | [-2^16-1^,2^16-1^-1] | 0 |
int (默认) |
32位,4字节 | [-2^32-1^,2^32-1^-1] | 0 |
long |
64位,8字节 | [-2^64-1^,2^64-1^-1] | 0L |
在定义这四个类型的变量时,需要注意定义的整数不要超过其存储范围(尤其是byte
类型,因为它的存储范围最小)。
例如:我想定义一个byte
、short
、int
类型的变量并赋值,其代码如下:
java
/**
* 基本数据类型之整数类型byte、short、int
*
* @author iCode504
* @date 2023-09-06 09:44:46
*/
public class BasicType {
public static void main(String[] args) {
byte b = 20;
short s = 30;
int i = 40;
System.out.println(b);
System.out.println(s);
System.out.println(i);
}
}
运行结果:
在使用long
类型的时候需要注意:long
类型的数据后面需要加上L
或l
(不推荐小写l
,因为小写l
很有可能和数字1或者大写字母I混淆)。示例代码如下:
java
/**
* 基本数据类型之整数类型long的使用
*
* @author iCode504
* @date 2023-09-06 09:53:04
*/
public class BasicTypeLong {
public static void main(String[] args) {
long num1 = 88;
long num2 = 6666666666666666666L;
System.out.println(num1);
System.out.println(num2);
}
}
运行结果:
细心的小伙伴会发现,为什么变量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字节 | [-2^128^,2^128^] | f 或F (必须写后缀) |
0.0f或0.0F | 7位小数 |
double |
64位,8字节 | [-2^1024^,2^1024^] | d 或D (非强制要求,一般不写后缀) |
0.0 | 15位小数 |
示例代码如下:
java
/**
* 基础数据类型float和double
*
* @author iCode504
* @date 2023-09-06 15:25:05
*/
public class BasicTypeFloatAndDouble {
public static void main(String[] args) {
float f1 = 3.88f;
float f2 = 6.44F; // 使用F或f最为后缀都可以
float f3 = 6;
float f4 = 10.0f / 3f;
System.out.println(f1);
System.out.println(f2);
System.out.println(f3);
System.out.println(f4);
double d1 = 6.66D; // double的后缀可有可无
double d2 = 8;
double d3 = 10.0 / 3;
System.out.println(d1);
System.out.println(d2);
System.out.println(d3);
}
}
运行结果为:
为什么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
类型,用来存储单个字符,使用单引号和单个字符表示,因此在单引号中写多个字符是错误写法:
java
char ch1 = 'i'; // 正确写法
char ch2 = 'ijk'; // 错误写法,单引号中只能写一个字符!
char
是一个单一的16位的Unicode字符,它的存储范围是[0,65535]
,即'\u0000'
到'\uffff'
。
这里会有小伙伴问:char
不是只能表示单个字符吗?这就要说到Unicode字符表了,这个表存储了所有的字符(各种符号、中文英文等各种字符),Unicode字符表中的每个字符默认使用的是以\u
和十六进制数组合表示,也就是说\u0000
就是一个Unicode值,这个Unicode值对应着字符表中的一个字符。
Unicode字符表中存储了所有的可用的字符,\u0000
其实表示的时候Unicode字符表中第一个字符,编写测试代码如下:
java
/**
* 基本数据类型之char
*
* @author iCode504
* @date 2023-09-06 16:18:20
*/
public class BasicTypeChar1 {
public static void main(String[] args) {
// 使用Unicode字符表中的字符来初始化char类型的变量
char ch1 = '\u0000';
// 会输出,但是无法在控制台显示出来
System.out.println(ch1);
// 利用if方法判断ch1是否是Unicode字符表中的第一个字符
if (ch1 == '\u0000') {
System.out.println("ch1是Unicode字符表中的第一个字符");
} else {
System.out.println("ch1不是第一个字符");
}
}
}
运行结果如下:
代码中我写了三个输出语句,其中第一个直接输出这个字符,但是从运行结果中我们发现这个语句确实输出了,但是控制台无法显示这个字符。
为了进一步验证输出的字符是否是Unicode字符表第一个字符,这里我使用了一个if
判断。如果我们定义变量和\u0000
相等时,输出ch1是Unicode字符表中的第一个字符
,此时也就说明了第一个字符确实在计算机中存在,只是无法正常显示;相反,\u0000
并不是Unicode字符表中的第一个字符。运行结果正如我们所料,输出的内容是ch1是Unicode字符表中的第一个字符。
布尔类型
boolean
类型,即布尔类型,它只有两个值:true
(真)和false
(假)。通常用于条件表达式的判断(条件表达式后续文章会讲到),例如:我们都知道20 > 30是假,即判断结果为false
。
java
/**
* 基本数据类型之布尔类型
*
* @author iCode504
* @date 2023-09-06 15:33:15
*/
public class BasicTypeBoolean {
public static void main(String[] args) {
boolean bool1 = true;
boolean bool2 = false;
System.out.println(bool1);
System.out.println(bool2);
System.out.println(20 > 30);
}
}
运行结果:
数字的进制表示(了解)
在中学期间我们学过数字有二进制、八进制、十进制和十六进制。
- 二进制数字是由0、1组成,满二进一。
- 八进制数字是由0~7组成,满八进一。
- 十六进制是由0~9、A、B、C、D、E、F组成,满十六进一
日常我们表示数字都是采用十进制,Java程序表示数字亦是如此。那么,如何表示二进制、八进制、十六进制的数字?
以十进制的数字22
为例,转换为各个进制的数字如下:
二进制 | 八进制 | 十六进制 |
---|---|---|
10110 | 26 | 16 |
在Java中,表示二进制数字,需要在数字前面加上0B
或0b
;如果表示八进制数字,需要在数字前面加上0
即可;如果是十六进制的数字,需要在数字前面加上0X
或者0x
,以下是示例代码:
java
/**
* 基本数据类型之整数类型byte、short、int
*
* @author iCode504
* @date 2023-09-06 10:32:17
*/
public class BaseRepresentation {
public static void main(String[] args) {
// 十进制数字
int decimal = 22;
// 二进制数字
int binary = 0B10110;
// 八进制数字
int octal = 026;
// 十六进制数字
int hexadecimal = 0x16;
System.out.println(decimal);
System.out.println(binary);
System.out.println(octal);
System.out.println(hexadecimal);
}
}
运行结果:
从运行结果我们可以看出:输出的数字无论是哪一种进制,默认都会转换为十进制的数字22
。
如果我想直接将十进制数字22
转换为各个进制并进行输出。
例如:我想定义的变量是int
类型,可以使用int
的包装类Integer
,在Integer
类中有和进制转换相关的方法:
toBinaryString(num)
:将十进制数字转换为二进制数字并表示。toOctalString(num)
:将十进制数字转换为八进制数字并表示。toHexString(num)
:将十进制数字转换为十六进制数字并表示。
java
/**
* 十进制数字转换为各个进制并输出
*
* @author iCode504
* @date 2023-09-06 10:43:53
*/
public class BaseRepresentationParse {
public static void main(String[] args) {
// 十进制数字
int number = 22;
// 转换为二进制数字并输出
System.out.println(Integer.toBinaryString(number));
// 转换为八进制数字并输出
System.out.println(Integer.toOctalString(number));
// 转换为十六进制数字并输出
System.out.println(Integer.toHexString(number));
}
}
运行结果:
原码、反码、补码(了解)
原码、反码、补码是计算机中表示数值的一种方式,主要应用于计算机的加减运算。
原码是最基本的表示方法, 直接将数值以二进制的形式表示,原码就是符号位加上真值的绝对值,即第一位表示正负号(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
类型,代码如下:
java
/**
* 自动类型提升
*
* @author iCode504
* @date 2023-09-06 15:42:38
*/
public class TypePromotion1 {
public static void main(String[] args) {
short s1 = 16;
// short类型自动类型提升为int类型
int i1 = s1;
System.out.println(i1);
}
}
运行结果:
上述案例可以看出,s1
赋值给i1
的时候并没有报错,原因就在于s1
自动转换为int
类型的值赋给i1
。
自动类型提升可能存在的特殊情况:
情况一:当byte
、short
、char
三者互相参与运算时,默认转为int
类型。
java
/**
* 自动类型提升:参与运算
*
* @author iCode504
* @date 2023-09-06 15:45:36
*/
public class TypePromotion2 {
public static void main(String[] args) {
byte num1 = 20;
short num2 = 30;
char ch1 = 'a';
// 以下三种运算均为错误写法,因为运算过程中byte、short、char类型的变量num1、num2和ch1会自动提升为int类型进行运算
// 得到的结果和左侧变量的数据类型不匹配而导致报错
// byte num3 = num1 + num2;
// short num4 = num1 + ch1;
// char ch3 = num1 + num2;
}
}
当我们解除一个错误写法的注释(例如byte num3 = num1 + num2;
)。我们可以执行javac
命令查询错误信息:
情况二:整数类型向浮点类型转换时,默认后面会带.0
。
java
/**
* 自动类型提升:整型向浮点类型进行转换
*
* @author iCode504
* @date 2023-09-06 15:56:40
*/
public class TypePromotion3 {
public static void main(String[] args) {
int num1 = 20;
// num1由int类型自动提升为float类型
float num2 = num1;
System.out.println(num1);
System.out.println(num2);
}
}
运行结果:
情况三:char
类型向更高数据范围(例如:int
、long
等)转换时,以数字的形式输出。
java
/**
* 自动类型提升:字符类型char转换成其他类型
*
* @author iCode504
* @date 2023-09-06 16:00:43
*/
public class TypePromotion4 {
public static void main(String[] args) {
// char类型转换成int类型
char ch1 = 'i';
int num1 = ch1;
System.out.println(num1);
// char类型转换成long类型
char ch2 = 'C';
long num2 = ch2;
System.out.println(num2);
// char类型转换成double类型
char ch3 = 'O';
double num3 = ch3;
System.out.println(num3);
}
}
强制类型转换
和自动类型提升相比,强制类型转换正好相反,由大范围的数据类型向小范围的数据类型进行转换,转换格式如下:
java
小数据类型 变量名 = (小数据类型) 大类型数据
如果我想将long
类型的数据转换为byte
、short
、int
类型的数据,由于long
是大范围的数据类型,向这三个小范围数据类型转换时需要进行强制类型转换。以下是示例代码:
java
/**
* 强制类型转换:long类型转换为byte、short、int类型
*
* @author iCode504
* @date 2023-09-06 11:16:21
*/
public class CastType {
public static void main(String[] args) {
// 定义一个long类型变量
long longValue = 108L;
// 强制把long类型转换为int类型
int intValue = (int) longValue;
// 输出int变量
System.out.println(intValue);
// 强制把long类型转换为short类型
short shortValue = (short) longValue;
// 输出short变量
System.out.println(shortValue);
// 强制把long类型转换为byte类型
byte byteValue = (byte) longValue;
// 输出byte变量
System.out.println(byteValue);
}
}
运行结果:
当然,强制类型转换也会存在如下的情况:
情况一:浮点类型转换成整数类型时,会出现精度损失,即小数点会被截断(不会四舍五入),只保留整数部分。
java
/**
* 强制类型转换情况一:浮点类型转换为整数类型
*
* @author iCode504
* @date 2023-09-06 11:21:54
*/
public class CastType1 {
public static void main(String[] args) {
// 定义一个double类型变量
double doubleValue = 9.06;
// 强制把double类型转换为int类型
int intValue = (int) doubleValue;
// 输出int变量
System.out.println(intValue);
// 定义一个float类型变量
float floatValue = 5.06f;
// 强制把float类型转换为int类型
intValue = (int) floatValue;
// 输出int变量
System.out.println(intValue);
}
}
运行结果:
情况二:要转换的数字超出目标类型的范围,Java会自动对整数进行溢出处理,不会得到预期的值。
例如:定义一个int
类型的变量130
,将其转换成byte
类型,而byte
类型的存储范围是[-128,127]
,130
很明显超出了这个范围,强制转换的结果不会符合我们的预期,示例代码如下:
java
/**
* 强制类型转换情况二:要转换的数字超出目标类型的范围,得到的结果不符合预期
*
* @author iCode504
* @date 2023-09-06 11:28:48
*/
public class CastType2 {
public static void main(String[] args) {
// 定义一个int类型变量,值是超出byte类型的范围
int intValue = 130;
// 强制把int类型转换为byte类型
byte byteValue = (byte) intValue;
// 输出byte变量
System.out.println(byteValue);
}
}
很明显,输出结果并不符合我们的预期,而是得到了值-126
,接下来我们从底层角度进行分析:
由于int
为4字节32位,每一位是由二进制的0和1表示,因此130转换成二进制数(32位)为:
int
类型强制转换成byte
类型以后,只保留后八位,结果如下:
得到的10000010
是源码,8位的byte
第一位是符号位,0表示正号,1表示负号。很明显这个数是负数,表示负数需要先将原码转换成反码,反码变成补码,补码再转换成十进制数字以后就是byte
类型的结果。首先我们先将其转换成反码(符号位除外):
将反码加1之后,就得到补码:
将11111110
转换成十进制数为(第1位是符号位,是负数):
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> − ( 1 × 2 6 + 1 × 2 5 + 1 × 2 4 + 1 × 2 3 + 1 × 2 2 + 1 × 2 1 + 0 × 2 0 ) = − 126 -(1\times2^{6}+1\times2^{5}+1\times2^{4}+1\times2^{3}+1\times2^{2}+1\times2^{1}+0\times2^{0})=-126 </math>−(1×26+1×25+1×24+1×23+1×22+1×21+0×20)=−126
因此强制类型转换得到的结果是-126
。
情况三:byte
、short
、char
进行运算时,会被提升为int
类型,然后再进行计算。要想转换成小范围数据类型,需要进行强制类型转换。
以下写法无法通过编译而报错:
java
/**
* 强制类型转换情况一:byte、short、char进行运算时,会自动提升为int类型
*
* @author iCode504
* @date 2023-09-06 14:17:31
*/
public class CastType3 {
public static void main(String[] args) {
byte byteValue = 20;
short shortValue = 30;
char charValue = 'A';
// 以下写法都是错误的,因为运算过程中byte、short、char会自动提升为int类型
byte byteResult = byteValue + shortValue;
short shortResult = shortValue - charValue;
char charResult = charValue + 10;
System.out.println(byteResult);
System.out.println(shortResult);
System.out.println(charResult);
}
}
无法通过编译,因为进行加减法运算时,变量会自动提升为int
类型,得到的结果也是int
类型,和左侧原有的数据类型不匹配而报错:
正确的写法是:将得到的结果进行强制类型转换:
java
/**
* 强制类型转换情况一:byte、short、char进行运算时,会自动提升为int类型
*
* @author iCode504
* @date 2023-09-06 14:17:31
*/
public class CastType3 {
public static void main(String[] args) {
byte byteValue = 20;
short shortValue = 30;
char charValue = 'A';
// 以下写法都是错误的,因为运算过程中byte、short、char会自动提升为int类型
// byte byteResult = byteValue + shortValue;
// short shortResult = shortValue - charValue;
// char charResult = charValue + 10;
byte byteResult = (byte) (byteValue + shortValue);
short shortResult = (short) (shortValue - charValue);
char charResult = (char) (charValue + 10);
System.out.println(byteResult);
System.out.println(shortResult);
System.out.println(charResult);
}
}
运行结果符合预期: