一、引言:数据类型的核心地位
1.1 一切从数据开始
编程的本质是什么?如果用一句话来概括,那就是"处理数据"。无论是简单的计算器程序,还是复杂的电商交易系统,其核心工作都是接收数据、处理数据、输出数据。而数据类型(Data Type)就是我们对数据进行分类和组织的基本方式。
Java是一门强类型(Strongly Typed)编程语言。这意味着在Java程序中,每一个变量都必须先声明其数据类型,然后才能使用。这个看似"麻烦"的规定,实际上是Java保证程序正确性和安全性的重要机制。编译器在程序运行之前就能检查出类型不匹配的错误,避免了许多潜在的运行时问题。
#mermaid-svg-gdVwlVyI87MyxglQ{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gdVwlVyI87MyxglQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gdVwlVyI87MyxglQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gdVwlVyI87MyxglQ .error-icon{fill:#552222;}#mermaid-svg-gdVwlVyI87MyxglQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gdVwlVyI87MyxglQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gdVwlVyI87MyxglQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gdVwlVyI87MyxglQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gdVwlVyI87MyxglQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gdVwlVyI87MyxglQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gdVwlVyI87MyxglQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gdVwlVyI87MyxglQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gdVwlVyI87MyxglQ .marker.cross{stroke:#333333;}#mermaid-svg-gdVwlVyI87MyxglQ svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gdVwlVyI87MyxglQ p{margin:0;}#mermaid-svg-gdVwlVyI87MyxglQ .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-gdVwlVyI87MyxglQ .cluster-label text{fill:#333;}#mermaid-svg-gdVwlVyI87MyxglQ .cluster-label span{color:#333;}#mermaid-svg-gdVwlVyI87MyxglQ .cluster-label span p{background-color:transparent;}#mermaid-svg-gdVwlVyI87MyxglQ .label text,#mermaid-svg-gdVwlVyI87MyxglQ span{fill:#333;color:#333;}#mermaid-svg-gdVwlVyI87MyxglQ .node rect,#mermaid-svg-gdVwlVyI87MyxglQ .node circle,#mermaid-svg-gdVwlVyI87MyxglQ .node ellipse,#mermaid-svg-gdVwlVyI87MyxglQ .node polygon,#mermaid-svg-gdVwlVyI87MyxglQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-gdVwlVyI87MyxglQ .rough-node .label text,#mermaid-svg-gdVwlVyI87MyxglQ .node .label text,#mermaid-svg-gdVwlVyI87MyxglQ .image-shape .label,#mermaid-svg-gdVwlVyI87MyxglQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-gdVwlVyI87MyxglQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-gdVwlVyI87MyxglQ .rough-node .label,#mermaid-svg-gdVwlVyI87MyxglQ .node .label,#mermaid-svg-gdVwlVyI87MyxglQ .image-shape .label,#mermaid-svg-gdVwlVyI87MyxglQ .icon-shape .label{text-align:center;}#mermaid-svg-gdVwlVyI87MyxglQ .node.clickable{cursor:pointer;}#mermaid-svg-gdVwlVyI87MyxglQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-gdVwlVyI87MyxglQ .arrowheadPath{fill:#333333;}#mermaid-svg-gdVwlVyI87MyxglQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-gdVwlVyI87MyxglQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-gdVwlVyI87MyxglQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gdVwlVyI87MyxglQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-gdVwlVyI87MyxglQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gdVwlVyI87MyxglQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-gdVwlVyI87MyxglQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-gdVwlVyI87MyxglQ .cluster text{fill:#333;}#mermaid-svg-gdVwlVyI87MyxglQ .cluster span{color:#333;}#mermaid-svg-gdVwlVyI87MyxglQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-gdVwlVyI87MyxglQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-gdVwlVyI87MyxglQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-gdVwlVyI87MyxglQ .icon-shape,#mermaid-svg-gdVwlVyI87MyxglQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-gdVwlVyI87MyxglQ .icon-shape p,#mermaid-svg-gdVwlVyI87MyxglQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-gdVwlVyI87MyxglQ .icon-shape .label rect,#mermaid-svg-gdVwlVyI87MyxglQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-gdVwlVyI87MyxglQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-gdVwlVyI87MyxglQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-gdVwlVyI87MyxglQ :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Java数据类型
基本数据类型 Primitive Types
引用数据类型 Reference Types
整数类型
浮点类型
字符类型 char
布尔类型 boolean
byte 1字节
short 2字节
int 4字节
long 8字节
float 4字节
double 8字节
类 Class
接口 Interface
数组 Array
枚举 Enum
String
1.2 为什么需要这么多数据类型?
初学者可能会困惑:为什么Java需要定义这么多不同的数据类型?不能所有数据都用一种类型表示吗?
答案在于计算机的底层工作原理。计算机的内存(Memory)是由数以亿计的存储单元组成的,每个单元可以存储一个0或1(即1个bit,比特)。不同类型的数据需要不同数量的内存空间,也有着不同的运算规则。例如:
- 存储年龄只需要一个小的整数(如0到150),使用1个字节就够了
- 存储全国人口数量需要一个很大的整数(约14亿),需要4个字节
- 存储圆周率π需要带小数的数值,需要特殊的浮点数格式
- 存储姓名需要一个字符序列,需要字符串类型
合理选择数据类型,可以在保证数值范围的前提下,最大限度地节约内存空间。这在处理海量数据的场景下(如大数据分析)尤为重要。
二、基本数据类型
2.1 整数类型家族
Java提供了四种整数类型,它们的主要区别在于占用的内存大小和表示的数值范围。选择哪种整数类型,取决于你要存储的数据范围。
| byte | 1 | 8 | -128 ~ 127 (-2^7 ~ 2^7-1) | 0 | Byte |
| short | 2 | 16 | -32768 ~ 32767 (-2^15 ~ 2^15-1) | 0 | Short |
| int | 4 | 32 | -2^31 ~ 2^31-1 (约±21亿) | 0 | Integer |
| long | 8 | 64 | -2^63 ~ 2^63-1 | 0L | Long |
byte类型是最小的整数类型,占用仅1个字节。它通常用于处理二进制数据(如文件读写、网络传输中的字节流),或者在数组中节省内存空间。在实际开发中,byte较少用于普通的数值计算,更多出现在IO操作和网络编程中。
public class ByteDemo {
public static void main(String[] args) {
byte minByte = –128; // byte的最小值
byte maxByte = 127; // byte的最大值
byte defaultByte = 0; // byte的默认值(成员变量)
System.out.println("byte最小值: " + minByte);
System.out.println("byte最大值: " + maxByte);
// 字节数组在文件读写中的使用
byte[] fileData = {0x48, 0x65, 0x6C, 0x6C, 0x6F};
// 这5个字节代表ASCII码中的"Hello"
// 溢出问题演示
// byte b = 128; // 编译错误!128超出byte范围
byte b = 127;
b++; // b变成了-128(溢出回绕)
System.out.println("127 + 1 = " + b); // 输出: -128
}
}
short类型占用2个字节,取值范围约为±3万。在实际开发中,short使用频率相对较低。因为int类型已经能够满足大多数场景下的整数需求,且现代计算机的内存已经足够大,节省2个字节的意义不大。short偶尔用于需要与C/C++程序交互的场景(如JNI开发)。
int类型是Java中最常用的整数类型。它占用4个字节(32位),取值范围约为±21亿。这个范围对于绝大多数业务场景来说足够了——用户ID、订单数量、年龄、计数器等都可以使用int表示。当你不确定该用哪种整数类型时,选int通常是一个合理的选择。
public class IntDemo {
public static void main(String[] args) {
int userId = 10001; // 用户ID
int orderCount = 999; // 订单数量
int age = 25; // 年龄
int maxInt = Integer.MAX_VALUE; // int最大值: 2147483647
int minInt = Integer.MIN_VALUE; // int最小值: -2147483648
System.out.println("int最大值: " + maxInt);
System.out.println("int最小值: " + minInt);
// 超过int范围会溢出
int overflow = maxInt + 1;
System.out.println("最大值+1 = " + overflow); // 输出: -2147483648
// 不同进制的字面量表示
int decimal = 100; // 十进制
int octal = 0144; // 八进制(以0开头)
int hex = 0x64; // 十六进制(以0x开头)
int binary = 0b01100100; // 二进制(以0b开头,Java 7+)
System.out.println("十进制100 = " + decimal);
System.out.println("八进制0144 = " + octal);
System.out.println("十六进制0x64 = " + hex);
System.out.println("二进制0b01100100 = " + binary);
}
}
long类型是最大的整数类型,占用8个字节(64位),可以表示极其巨大的整数。当需要表示超出int范围的数值时(如全球人口数、天文距离、高精度时间戳等),就需要使用long类型。在定义long字面量时,需要在数字后面加上字母L(或小写l,但建议用大写L以免和数字1混淆)。
public class LongDemo {
public static void main(String[] args) {
long worldPopulation = 8000000000L; // 世界人口,超过int范围
long timestamp = System.currentTimeMillis(); // 当前时间戳(毫秒)
long maxLong = Long.MAX_VALUE; // 9223372036854775807
System.out.println("当前时间戳: " + timestamp);
System.out.println("long最大值: " + maxLong);
// long作为ID的常见场景——雪花算法生成的分布式ID
long snowflakeId = 1234567890123456789L;
// 不使用L后缀会导致编译错误
// long tooBig = 9999999999; // 编译错误!整数字面量默认是int
long correct = 9999999999L; // 正确!
}
}
2.2 浮点类型:小数的世界
在科学计算、金融分析、图形渲染等场景中,仅仅能表示整数是远远不够的。Java提供了两种浮点类型来表示带小数点的数值。
| float | 4 | 32 | 约7位 | ±3.4E-38 ~ ±3.4E+38 | 0.0f | Float |
| double | 8 | 64 | 约15-16位 | ±1.7E-308 ~ ±1.7E+308 | 0.0d | Double |
float类型(单精度浮点数)占用4个字节,能够表示约7位有效数字。定义float字面量时,需要在数字后面加上f或F。
double类型(双精度浮点数)占用8个字节,能够表示约15-16位有效数字。它是Java中默认的浮点数类型——当我们写3.14时,Java默认将其视为double类型。
public class FloatDoubleDemo {
public static void main(String[] args) {
// float定义需要f/F后缀
float piFloat = 3.141592653589793f;
// double是默认浮点类型
double piDouble = 3.141592653589793;
System.out.println("float精度: " + piFloat); // 3.1415927(精度丢失)
System.out.println("double精度: " + piDouble); // 3.141592653589793(精度保持)
// 科学计数法
double avogadro = 6.02214076e23; // 阿伏伽德罗常数
double electronMass = 9.10938356e-31; // 电子质量
// 特殊浮点值
double posInfinity = Double.POSITIVE_INFINITY; // 正无穷
double negInfinity = Double.NEGATIVE_INFINITY; // 负无穷
double nan = Double.NaN; // 非数值
System.out.println("正无穷: " + posInfinity);
System.out.println("1.0/0.0 = " + (1.0 / 0.0)); // Infinity
System.out.println("0.0/0.0 = " + (0.0 / 0.0)); // NaN
}
}
浮点数的精度问题是一个非常重要的概念。浮点数在计算机中使用IEEE 754标准表示,这种表示方法无法精确表示所有的小数。例如,十进制的0.1在二进制中是一个无限循环小数,因此float和double都只能存储它的近似值。
public class FloatPrecisionDemo {
public static void main(String[] args) {
// 经典案例:0.1 + 0.2 不等于 0.3
double a = 0.1;
double b = 0.2;
double c = 0.3;
System.out.println("0.1 + 0.2 = " + (a + b)); // 0.30000000000000004
System.out.println("0.1 + 0.2 == 0.3 ? " + (a + b == c)); // false!
// 这就是为什么金融计算不推荐使用float和double
// 应该使用BigDecimal(后面的文章会详细讲解)
// float和double的比较应该使用误差范围
double epsilon = 0.0000001; // 允许的误差
boolean isEqual = Math.abs((a + b) – c) < epsilon;
System.out.println("使用误差范围比较: " + isEqual); // true
}
}
2.3 字符类型char:从ASCII到Unicode
char类型用于表示单个字符。在Java中,char占用2个字节(16位),使用Unicode编码。这意味着char可以表示65536个不同的字符,涵盖了世界上几乎所有语言的字符,包括中文、日文、韩文、阿拉伯文等。
public class CharDemo {
public static void main(String[] args) {
// 字符的四种表示方式
char ch1 = 'A'; // 直接用字符表示
char ch2 = 65; // 用Unicode码点表示(65是'A'的码点)
char ch3 = '\\u0041'; // 用Unicode转义序列表示
char ch4 = '中'; // 中文字符
System.out.println("ch1 = " + ch1); // A
System.out.println("ch2 = " + ch2); // A
System.out.println("ch3 = " + ch3); // A
System.out.println("ch4 = " + ch4); // 中
// char的本质:一个无符号16位整数
System.out.println("'A'的码点值: " + (int) ch1); // 65
System.out.println("'中'的码点值: " + (int) ch4); // 20013
// char的运算
char nextChar = (char) (ch1 + 1);
System.out.println("'A'的下一个字母: " + nextChar); // B
// 遍历26个大写字母
for (char c = 'A'; c <= 'Z'; c++) {
System.out.print(c + " ");
}
System.out.println();
// 转义字符
System.out.println("换行符: Hello\\nWorld");
System.out.println("制表符: Name\\tAge");
System.out.println("双引号: \\"Hello\\"");
System.out.println("反斜杠: C:\\\\Users\\\\Java");
}
}
char在计算机内部存储的是Unicode码点值。需要注意的是,Java中的char使用UTF-16编码。对于BMP(Basic Multilingual Plane,基本多语言平面)范围内的字符(码点U+0000到U+FFFF,也就是绝大多数常用字符),一个char就够了。但对于补充字符(如某些罕见汉字和emoji表情),则需要两个char来表示(称为代理对,Surrogate Pair),这在处理字符串时需要特别注意。
2.4 布尔类型boolean:非真即假
boolean是Java中最简单的数据类型,它只有两个可能的值:true和false。boolean类型用于表示逻辑判断的结果,在条件语句和循环控制中起着核心作用。
public class BooleanDemo {
public static void main(String[] args) {
boolean isJavaFun = true; // Java很有趣
boolean isFishMammal = false; // 鱼是哺乳动物?否
// boolean常用于条件判断
int score = 85;
boolean isPassed = score >= 60;
System.out.println("成绩通过了吗?" + isPassed); // true
// 逻辑运算
boolean hasTicket = true;
boolean hasID = true;
boolean canEnter = hasTicket && hasID; // 逻辑与:两个条件都满足
System.out.println("可以进场吗?" + canEnter); // true
boolean isVIP = true;
boolean canSkipLine = isVIP || score > 90; // 逻辑或:满足一个即可
System.out.println("可以免排队吗?" + canSkipLine); // true
// 逻辑非
boolean isWeekend = false;
boolean isWorkday = !isWeekend;
System.out.println("是工作日吗?" + isWorkday); // true
}
}
值得注意的是,与C/C++不同,Java中的boolean类型和整数类型是完全不兼容的。你不能将boolean转换为int,也不能将0和1当作false和true使用。这一设计使得Java程序更加类型安全和语义清晰。
2.5 基本类型的默认值与初始化
当基本类型的变量作为类的成员变量(即实例变量或静态变量)时,它们会被自动初始化为默认值。但当它们作为局部变量时,Java不会自动初始化,必须显式赋值后才能使用,否则编译器会报错。
public class DefaultValueDemo {
// 成员变量——会被自动初始化
byte memberByte;
short memberShort;
int memberInt;
long memberLong;
float memberFloat;
double memberDouble;
char memberChar;
boolean memberBoolean;
public void showDefaults() {
System.out.println("=== 成员变量默认值 ===");
System.out.println("byte: " + memberByte); // 0
System.out.println("short: " + memberShort); // 0
System.out.println("int: " + memberInt); // 0
System.out.println("long: " + memberLong); // 0
System.out.println("float: " + memberFloat); // 0.0
System.out.println("double: " + memberDouble); // 0.0
System.out.println("char: [" + memberChar + "]"); // '\\u0000'(空字符)
System.out.println("boolean: " + memberBoolean); // false
}
public void localVariableIssue() {
// 局部变量——不会被自动初始化
int localVar;
// System.out.println(localVar); // 编译错误!变量未初始化
localVar = 100;
System.out.println("初始化后: " + localVar); // 100
}
public static void main(String[] args) {
DefaultValueDemo demo = new DefaultValueDemo();
demo.showDefaults();
demo.localVariableIssue();
}
}
三、引用数据类型
3.1 什么是引用数据类型?
引用数据类型是Java中与基本数据类型并列的另一大类。如果说基本数据类型直接存储的是"值"本身,那么引用数据类型存储的是对象在内存中的"地址"(即引用)。
可以这样理解:基本类型变量就像是一个"盒子",盒子里直接装着数据;引用类型变量也像是一个"盒子",但盒子里装的不是数据本身,而是一张写着"数据在某某位置"的纸条。通过这张纸条(引用),我们才能找到真正的数据(对象)。
#mermaid-svg-R4VPHr1UM7sUONKB{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-R4VPHr1UM7sUONKB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-R4VPHr1UM7sUONKB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-R4VPHr1UM7sUONKB .error-icon{fill:#552222;}#mermaid-svg-R4VPHr1UM7sUONKB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-R4VPHr1UM7sUONKB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-R4VPHr1UM7sUONKB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-R4VPHr1UM7sUONKB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-R4VPHr1UM7sUONKB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-R4VPHr1UM7sUONKB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-R4VPHr1UM7sUONKB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-R4VPHr1UM7sUONKB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-R4VPHr1UM7sUONKB .marker.cross{stroke:#333333;}#mermaid-svg-R4VPHr1UM7sUONKB svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-R4VPHr1UM7sUONKB p{margin:0;}#mermaid-svg-R4VPHr1UM7sUONKB .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-R4VPHr1UM7sUONKB .cluster-label text{fill:#333;}#mermaid-svg-R4VPHr1UM7sUONKB .cluster-label span{color:#333;}#mermaid-svg-R4VPHr1UM7sUONKB .cluster-label span p{background-color:transparent;}#mermaid-svg-R4VPHr1UM7sUONKB .label text,#mermaid-svg-R4VPHr1UM7sUONKB span{fill:#333;color:#333;}#mermaid-svg-R4VPHr1UM7sUONKB .node rect,#mermaid-svg-R4VPHr1UM7sUONKB .node circle,#mermaid-svg-R4VPHr1UM7sUONKB .node ellipse,#mermaid-svg-R4VPHr1UM7sUONKB .node polygon,#mermaid-svg-R4VPHr1UM7sUONKB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-R4VPHr1UM7sUONKB .rough-node .label text,#mermaid-svg-R4VPHr1UM7sUONKB .node .label text,#mermaid-svg-R4VPHr1UM7sUONKB .image-shape .label,#mermaid-svg-R4VPHr1UM7sUONKB .icon-shape .label{text-anchor:middle;}#mermaid-svg-R4VPHr1UM7sUONKB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-R4VPHr1UM7sUONKB .rough-node .label,#mermaid-svg-R4VPHr1UM7sUONKB .node .label,#mermaid-svg-R4VPHr1UM7sUONKB .image-shape .label,#mermaid-svg-R4VPHr1UM7sUONKB .icon-shape .label{text-align:center;}#mermaid-svg-R4VPHr1UM7sUONKB .node.clickable{cursor:pointer;}#mermaid-svg-R4VPHr1UM7sUONKB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-R4VPHr1UM7sUONKB .arrowheadPath{fill:#333333;}#mermaid-svg-R4VPHr1UM7sUONKB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-R4VPHr1UM7sUONKB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-R4VPHr1UM7sUONKB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-R4VPHr1UM7sUONKB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-R4VPHr1UM7sUONKB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-R4VPHr1UM7sUONKB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-R4VPHr1UM7sUONKB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-R4VPHr1UM7sUONKB .cluster text{fill:#333;}#mermaid-svg-R4VPHr1UM7sUONKB .cluster span{color:#333;}#mermaid-svg-R4VPHr1UM7sUONKB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-R4VPHr1UM7sUONKB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-R4VPHr1UM7sUONKB rect.text{fill:none;stroke-width:0;}#mermaid-svg-R4VPHr1UM7sUONKB .icon-shape,#mermaid-svg-R4VPHr1UM7sUONKB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-R4VPHr1UM7sUONKB .icon-shape p,#mermaid-svg-R4VPHr1UM7sUONKB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-R4VPHr1UM7sUONKB .icon-shape .label rect,#mermaid-svg-R4VPHr1UM7sUONKB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-R4VPHr1UM7sUONKB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-R4VPHr1UM7sUONKB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-R4VPHr1UM7sUONKB :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
堆内存 Heap
栈内存 Stack
直接存储
存储引用地址
int num = 10
String str
String对象Hello
10
引用数据类型主要包括以下几类:
| 类(Class) | 自定义的类或Java标准库中的类 | String, Scanner, ArrayList, 自定义类 |
| 接口(Interface) | 定义行为规范的引用类型 | Runnable, Comparable, List |
| 数组(Array) | 存储同类型多个元素的容器 | int[], String[], double[][] |
| 枚举(Enum) | 表示一组固定常量的类型 | enum Season {SPRING, SUMMER} |
| 注解(Annotation) | 代码中的元数据标记 | @Override, @Deprecated |
3.2 引用类型的存储机制
让我们通过一个具体的例子来理解基本类型和引用类型在内存中的区别:
public class ReferenceDemo {
public static void main(String[] args) {
// 基本类型:直接存储值
int a = 10;
int b = a; // b拷贝了a的值
b = 20; // 修改b不影响a
System.out.println("a = " + a); // a仍然是10
System.out.println("b = " + b); // b是20
System.out.println("—");
// 引用类型:存储的是引用(地址)
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1; // arr2拷贝了arr1的引用(指向同一个数组对象)
arr2[0] = 999; // 通过arr2修改数组
System.out.println("arr1[0] = " + arr1[0]); // 999!arr1也变了
System.out.println("—");
// String的特殊性:不可变性
String s1 = "Hello";
String s2 = s1; // s2和s1指向同一个String对象
s2 = "World"; // s2现在指向了一个新的String对象
System.out.println("s1 = " + s1); // 仍然是"Hello"
System.out.println("s2 = " + s2); // "World"
}
}
从这个例子中可以看出:
- 基本类型变量之间的赋值是"值拷贝"——修改一个不影响另一个。
- 引用类型变量之间的赋值是"引用拷贝"——两个变量指向同一个对象,通过任何一个变量修改对象,都会影响另一个变量看到的结果。
- String虽然也是引用类型,但由于其不可变性,行为上表现得"像是"基本类型。
3.3 null:引用类型的特殊值
所有引用类型变量都可以被赋值为null,表示"不指向任何对象"。null是Java中的一个特殊字面量,是造成NullPointerException(空指针异常)的"罪魁祸首"——这是Java开发中最常见的运行时异常。
public class NullDemo {
public static void main(String[] args) {
String str = null; // str不指向任何字符串对象
int[] arr = null; // arr不指向任何数组对象
// System.out.println(str.length()); // NullPointerException!
// System.out.println(arr[0]); // NullPointerException!
// 使用前检查null
if (str != null) {
System.out.println("字符串长度: " + str.length());
} else {
System.out.println("str是null,无法获取长度");
}
// null的特殊性
System.out.println(null == null); // true(null只等于null自己)
// null可以被打印
System.out.println("null值: " + null); // 输出: null值: null
}
}
3.4 String类型初步
String是Java中最常用的引用类型之一。虽然String被大量使用,但很多初学者不了解它的特殊性——String对象一旦创建就不可修改(immutable,不可变的)。
public class StringIntroDemo {
public static void main(String[] args) {
// String的两种创建方式
String s1 = "Hello"; // 字面量方式(使用字符串常量池)
String s2 = new String("Hello"); // new关键字方式(在堆中创建新对象)
// 字符串比较
System.out.println(s1 == s2); // false(==比较引用地址)
System.out.println(s1.equals(s2)); // true(equals比较内容)
// 字符串常量池
String s3 = "Hello";
System.out.println(s1 == s3); // true(从常量池返回同一个对象)
// String的"修改"实际上是创建新对象
String original = "Hello";
String modified = original + " World"; // 创建了新String对象
System.out.println("original: " + original); // 仍然是"Hello"
System.out.println("modified: " + modified); // "Hello World"
// 常用String方法
String text = "Java Programming";
System.out.println("长度: " + text.length()); // 17
System.out.println("转大写: " + text.toUpperCase()); // JAVA PROGRAMMING
System.out.println("子串: " + text.substring(5)); // Programming
System.out.println("子串2: " + text.substring(0, 4)); // Java
System.out.println("是否包含: " + text.contains("gram")); // true
System.out.println("替换: " + text.replace("Java", "Kotlin")); // Kotlin Programming
}
}
关于String的更多高级话题(如字符串常量池、intern()方法、StringBuilder与StringBuffer的区别等),将在后续的"Java核心API篇"中进行深入探讨。
四、字面量与类型推断
4.1 各种字面量的表示方法
字面量(Literal)是指在代码中直接写出的固定值。Java支持多种字面量的表示方法:
public class LiteralDemo {
public static void main(String[] args) {
// 整数字面量
int dec = 255; // 十进制(默认)
int hex = 0xFF; // 十六进制(0x或0X前缀)
int oct = 0377; // 八进制(0前缀)
int bin = 0b11111111; // 二进制(0b或0B前缀,Java 7+)
System.out.println("十进制255: " + dec);
System.out.println("十六进制0xFF: " + hex);
System.out.println("八进制0377: " + oct);
System.out.println("二进制0b11111111: " + bin);
// 数字分隔符(Java 7+)
int million = 1_000_000; // 使用下划线提高可读性
long creditCard = 1234_5678_9012_3456L;
double pi = 3.141_592_653_589_793;
System.out.println("一百万: " + million);
System.out.println("信用卡号: " + creditCard);
System.out.println("圆周率: " + pi);
// 浮点字面量
float f = 3.14f; // float必须加f/F
double d1 = 3.14; // 默认是double
double d2 = 3.14d; // 显式加上d/D也可以
double sci = 1.5e3; // 科学计数法:1.5 × 10³ = 1500.0
System.out.println("科学计数1.5e3: " + sci);
// 字符和字符串字面量
char ch = 'A';
String str = "Hello, Java!";
// 布尔字面量
boolean yes = true;
boolean no = false;
}
}
4.2 类型推断var(Java 10+)
从Java 10开始,引入了局部变量类型推断功能。使用var关键字,编译器可以根据赋值表达式自动推断出变量的类型,省略显式的类型声明。这减少了代码中的"样板"书写,提升了可读性。
import java.util.ArrayList;
import java.util.HashMap;
public class VarDemo {
public static void main(String[] args) {
// 使用var简化变量声明
var name = "Java"; // 推断为String
var version = 21; // 推断为int
var price = 99.99; // 推断为double
var isLatest = true; // 推断为boolean
// var在复杂类型中的优势
var userMap = new HashMap<String, Integer>(); // 推断为HashMap<String, Integer>
var list = new ArrayList<String>(); // 推断为ArrayList<String>
// var的限制
// var x; // 错误!必须初始化
// var n = null; // 错误!无法推断
// var result = (a, b) -> a + b; // 错误!Lambda需要显式类型
// var只能用于局部变量
System.out.println("name类型: " + name.getClass().getSimpleName());
// 循环中使用var
var numbers = new int[]{1, 2, 3, 4, 5};
for (var num : numbers) {
System.out.print(num + " ");
}
}
}
需要注意的是,var并非Java变成了动态类型语言。类型推断发生在编译期,变量的类型一旦确定就不会改变。var只是一个语法糖,让代码更简洁,底层的类型安全机制完全没有改变。
五、类型使用中的常见问题
5.1 溢出问题
当运算结果超出数据类型的表示范围时,就会发生溢出(Overflow)。溢出不会抛出异常,而是会"静悄悄地"给出错误的结果,这在调试时非常危险。
public class OverflowDemo {
public static void main(String[] args) {
// 整数溢出
int maxInt = Integer.MAX_VALUE; // 2147483647
System.out.println("int最大值: " + maxInt);
System.out.println("int最大值+1: " + (maxInt + 1)); // -2147483648(溢出绕回)
// 整数下溢
int minInt = Integer.MIN_VALUE; // -2147483648
System.out.println("int最小值: " + minInt);
System.out.println("int最小值-1: " + (minInt – 1)); // 2147483647(溢出绕回)
// 如何防止溢出?使用Math.addExact等方法
try {
int safe = Math.addExact(maxInt, 1); // 会抛出异常
} catch (ArithmeticException e) {
System.out.println("检测到溢出!" + e.getMessage());
}
// 浮点数溢出
double big = 1.0e308;
double bigger = big * 10;
System.out.println("浮点溢出: " + bigger); // Infinity
}
}
5.2 精度丢失问题
浮点数无法精确表示某些十进制小数,这在金融计算中尤其需要注意。对于需要精确计算的场景,应该使用BigDecimal类。
import java.math.BigDecimal;
public class PrecisionDemo {
public static void main(String[] args) {
// 浮点数精度问题
double result1 = 1.0 – 0.9;
System.out.println("1.0 – 0.9 = " + result1); // 0.09999999999999998
double result2 = 0.1 + 0.1 + 0.1;
System.out.println("0.1 + 0.1 + 0.1 = " + result2); // 0.30000000000000004
// 使用BigDecimal解决精度问题
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("0.9");
BigDecimal result3 = a.subtract(b);
System.out.println("BigDecimal: 1.0 – 0.9 = " + result3); // 0.1
// 注意:不要用double构造BigDecimal
BigDecimal bad = new BigDecimal(0.1);
System.out.println("错误构造: new BigDecimal(0.1) = " + bad);
// 输出: 0.1000000000000000055511151231257827021181583404541015625
BigDecimal good = new BigDecimal("0.1"); // 用字符串构造!
System.out.println("正确构造: new BigDecimal(\\"0.1\\") = " + good); // 0.1
}
}
5.3 基本类型与包装类的选择
每种基本类型都有对应的包装类(Byte、Short、Integer、Long、Float、Double、Character、Boolean)。在实际开发中,什么时候用基本类型,什么时候用包装类呢?
基本类型的优势在于:内存占用小、访问速度快、没有null问题。包装类的优势在于:可以作为泛型类型参数、提供了丰富的工具方法(如Integer.parseInt())、可以为null(表示"无值")。
一般原则是:在不需要null语义且对性能敏感的场合,优先使用基本类型;在需要放入集合(List、Map等)或需要null语义时,使用包装类。
六、本章小结
本文对Java的两大数据类型体系——基本数据类型和引用数据类型——进行了全面而深入的探讨。
对于基本数据类型,我们详细剖析了四种整数类型(byte、short、int、long)、两种浮点类型(float、double)、字符类型(char)和布尔类型(boolean)的内存占用、取值范围和使用场景。对于引用数据类型,我们理解了"存储引用而非值"这一核心概念,并初步接触了String、数组等常见的引用类型。
数据类型是Java编程的基石。只有深入理解了数据类型,才能在后续的学习中更好地理解变量声明、类型转换、方法参数传递、集合框架等高级主题。从下一篇开始,我们将逐一深入整数类型家族,详细对比byte、short、int、long的区别与最佳实践。
本章关键词回顾
| 强类型语言 | 所有变量必须先声明类型才能使用的编程语言 |
| 基本类型 | Java中直接存储值的8种类型:byte/short/int/long/float/double/char/boolean |
| 引用类型 | 存储内存地址的类型:类、接口、数组、枚举、String |
| 字面量 | 在代码中直接写出的固定值,如数字100、字符串"Hello" |
| Unicode | 国际字符编码标准,Java中char使用Unicode编码 |
| 溢出 | 运算结果超出数据类型可表示范围的现象 |
| IEEE 754 | 浮点数的国际标准表示格式 |
| null | 引用类型的特殊值,表示不指向任何对象 |
| 不可变性 | String对象创建后内容无法修改的特性 |
| var | Java 10引入的局部变量类型推断关键字 |
思考与练习
下一篇文章:《整数类型:byte、short、int、long的区别与使用》,我们将对Java的四种整数类型进行更深入的对比分析,帮你建立正确的整数类型选择策略。


