数据类型
# 02.数据类型
# 目录介绍
# 2.1 基本数据
# 2.1.1 基本数据类型
Java 有 8 种基本数据类型(Primitive Types),它们直接存储值,而不是引用:
| 分类 | 类型 | 大小(字节) | 取值范围 |
|---|---|---|---|
| 整数 | byte | 1 | -128 ~ 127 |
| 整数 | short | 2 | -32768 ~ 32767 |
| 整数 | int | 4 | -2^31 ~ 2^31-1 |
| 整数 | long | 8 | -2^63 ~ 2^63-1 |
| 浮点 | float | 4 | ±3.4e38(6-7位有效数字) |
| 浮点 | double | 8 | ±1.8e308(15-16位有效数字) |
| 字符 | char | 2 | 0 ~ 65535(Unicode字符) |
| 布尔 | boolean | 1(逻辑上) | true / false |
对比 C++:Java 的基本类型大小是固定的,不受平台影响。而 C++ 的 int 在不同平台可能是 2 字节或 4 字节。Java 的 char 是 2 字节(Unicode),C++ 的 char 是 1 字节(ASCII)。
# 2.1.2 引用数据类型
除了 8 种基本类型,Java 中的其他类型都是引用类型(Reference Types),包括:
- 类(Class):如
String、Account - 接口(Interface):如
List、Runnable - 数组(Array):如
int[]、String[]
引用类型的变量存储的是对象在堆内存中的地址引用,而不是对象本身。
// 基本类型:直接存值
int a = 10;
// 引用类型:存储的是对象的地址
String name = "张三"; // name 存储的是 "张三" 这个 String 对象的地址
2
3
4
5
# 2.1.3 基本类型和引用类型区别
| 对比项 | 基本类型 | 引用类型 |
|---|---|---|
| 存储位置 | 栈内存 | 引用在栈,对象在堆 |
| 存储内容 | 直接存值 | 存对象的地址 |
| 默认值 | 有默认值(0, false等) | 默认值为 null |
| 比较方式 | == 比较值 | == 比较地址,equals 比较内容 |
| 传参方式 | 值传递(传副本) | 值传递(传引用的副本) |
对比 C++:C++ 中所有类型都可以在栈上创建,也可以用 new 在堆上创建。Java 的基本类型只能在栈上,对象只能在堆上(通过 new 创建)。
# 2.1.4 综合案例:数据类型信息打印器
编写一个程序,打印 Java 8 种基本数据类型的详细信息(类型名、占用字节、最小值、最大值),并演示基本类型和引用类型的区别。
public class DataTypeInfo {
public static void main(String[] args) {
// 打印8种基本数据类型信息
System.out.println("===== Java 8种基本数据类型 =====");
System.out.printf("%-10s %-6s %-25s %-25s%n", "类型", "字节", "最小值", "最大值");
System.out.println("-".repeat(70));
System.out.printf("%-10s %-6d %-25d %-25d%n", "byte", 1, Byte.MIN_VALUE, Byte.MAX_VALUE);
System.out.printf("%-10s %-6d %-25d %-25d%n", "short", 2, Short.MIN_VALUE, Short.MAX_VALUE);
System.out.printf("%-10s %-6d %-25d %-25d%n", "int", 4, Integer.MIN_VALUE, Integer.MAX_VALUE);
System.out.printf("%-10s %-6d %-25d %-25d%n", "long", 8, Long.MIN_VALUE, Long.MAX_VALUE);
System.out.printf("%-10s %-6d %-25s %-25s%n", "float", 4, Float.MIN_VALUE, Float.MAX_VALUE);
System.out.printf("%-10s %-6d %-25s %-25s%n", "double", 8, Double.MIN_VALUE, Double.MAX_VALUE);
System.out.printf("%-10s %-6d %-25d %-25d%n", "char", 2, (int) Character.MIN_VALUE, (int) Character.MAX_VALUE);
System.out.printf("%-10s %-6s %-25s %-25s%n", "boolean", "1*", "false", "true");
// 演示基本类型 vs 引用类型
System.out.println("\n===== 基本类型 vs 引用类型 =====");
int a = 100;
int b = a; // 值的拷贝
b = 200;
System.out.println("基本类型:a=" + a + ", b=" + b); // a不受影响
int[] arr1 = {1, 2, 3};
int[] arr2 = arr1; // 引用的拷贝(指向同一个数组)
arr2[0] = 999;
System.out.println("引用类型:arr1[0]=" + arr1[0] + ", arr2[0]=" + arr2[0]); // 都变了
// 默认值演示(成员变量)
System.out.println("\n===== 默认值演示 =====");
DefaultValues obj = new DefaultValues();
obj.printDefaults();
}
}
class DefaultValues {
byte byteVal;
short shortVal;
int intVal;
long longVal;
float floatVal;
double doubleVal;
char charVal;
boolean boolVal;
String strVal; // 引用类型
void printDefaults() {
System.out.println("byte默认值: " + byteVal); // 0
System.out.println("short默认值: " + shortVal); // 0
System.out.println("int默认值: " + intVal); // 0
System.out.println("long默认值: " + longVal); // 0
System.out.println("float默认值: " + floatVal); // 0.0
System.out.println("double默认值: " + doubleVal); // 0.0
System.out.println("char默认值: [" + charVal + "] (Unicode: " + (int) charVal + ")"); // \u0000
System.out.println("boolean默认值: " + boolVal); // false
System.out.println("String默认值: " + strVal); // null
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 2.1.5 训练题
填空题:Java 有 ___ 种基本数据类型,分别属于 ___ 大类(整数、浮点、字符、布尔)。
选择题:以下哪个是引用类型?
- A.
intB.doubleC.StringD.boolean
- A.
判断题:基本类型用
==比较的是值,引用类型用==比较的也是值。( )思考题:Java 的基本类型存储在栈内存中,引用类型的对象存储在堆内存中。请思考:为什么 Java 不像 C++ 那样允许对象也在栈上创建?这样设计有什么优缺点?
# 2.2 整数类型
作用:整型变量表示的是整数类型的数据
# 2.2.1 整型列表
| 数据类型 | 占用空间 | 取值范围 | 默认值 |
|---|---|---|---|
byte | 1字节 | -128 ~ 127 | 0 |
short | 2字节 | -32768 ~ 32767 | 0 |
int | 4字节 | -2^31 ~ 2^31-1 | 0 |
long | 8字节 | -2^63 ~ 2^63-1 | 0L |
示例:
public class Main {
public static void main(String[] args) {
byte b = 127;
short s = 32767;
int i = 2147483647;
long l = 9223372036854775807L; // long 类型需要加 L 后缀
System.out.println("byte: " + b);
System.out.println("short: " + s);
System.out.println("int: " + i);
System.out.println("long: " + l);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
对比 C++:Java 没有 unsigned 无符号类型(Java 8 中有部分无符号支持方法),C++ 有 unsigned int、unsigned long 等。
# 2.2.2 整数字面量
Java 支持多种进制的整数字面量:
int decimal = 100; // 十进制
int binary = 0b1100100; // 二进制(0b 开头)
int octal = 0144; // 八进制(0 开头)
int hex = 0x64; // 十六进制(0x 开头)
// Java 7 开始支持下划线分隔,提高可读性
int million = 1_000_000;
long creditCard = 1234_5678_9012_3456L;
2
3
4
5
6
7
8
# 2.2.3 整数溢出
Java 的整数溢出不会报错,而是"绕回"(类似 C++ 的无符号整数溢出行为):
int max = Integer.MAX_VALUE; // 2147483647
System.out.println(max + 1); // -2147483648(溢出绕回)
2
对比 C++:C++ 中有符号整数溢出是未定义行为(UB),Java 中整数溢出行为是确定的(模运算)。
# 2.2.4 综合案例:进制转换工具
编写一个程序,输入一个十进制整数,输出其各种进制表示,并演示整数溢出和不同整型的范围。
import java.util.Scanner;
public class IntegerConverter {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入一个十进制整数:");
long input = sc.nextLong();
// 进制转换
System.out.println("\n===== 进制转换 =====");
System.out.println("十进制: " + input);
System.out.println("二进制: " + Long.toBinaryString(input));
System.out.println("八进制: " + Long.toOctalString(input));
System.out.println("十六进制: " + Long.toHexString(input).toUpperCase());
// 使用下划线分隔提高可读性
System.out.println("\n===== 字面量写法示例 =====");
int million = 1_000_000;
int binary = 0b0000_1111;
int hex = 0xFF_FF;
System.out.println("1_000_000 = " + million);
System.out.println("0b0000_1111 = " + binary);
System.out.println("0xFF_FF = " + hex);
// 检查输入值能否装入不同整型
System.out.println("\n===== 类型适配检查 =====");
System.out.println("byte [-128, 127]: " + (input >= Byte.MIN_VALUE && input <= Byte.MAX_VALUE ? "✓ 可存储" : "✗ 超出范围"));
System.out.println("short [-32768, 32767]: " + (input >= Short.MIN_VALUE && input <= Short.MAX_VALUE ? "✓ 可存储" : "✗ 超出范围"));
System.out.println("int [-2^31, 2^31-1]: " + (input >= Integer.MIN_VALUE && input <= Integer.MAX_VALUE ? "✓ 可存储" : "✗ 超出范围"));
System.out.println("long [-2^63, 2^63-1]: ✓ 可存储");
// 溢出演示
System.out.println("\n===== 整数溢出演示 =====");
int maxInt = Integer.MAX_VALUE;
System.out.println("int最大值: " + maxInt);
System.out.println("int最大值+1: " + (maxInt + 1) + " (溢出!)");
System.out.println("用long接收: " + (maxInt + 1L) + " (正确)");
sc.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 2.2.5 训练题
选择题:
long l = 100;和long l = 100L;有什么区别?- A. 完全相同 B. 前者编译错误 C. 前者先创建 int 再自动转为 long D. 后者直接创建 long
代码题:请写出以下代码的输出:
int max = Integer.MAX_VALUE; System.out.println(max); System.out.println(max + 1); System.out.println(max + 1L);1
2
3
4思考题:Java 没有
unsigned无符号整数类型,如果需要存储一个超过int正数范围的无符号值(如网络协议中的 32 位无符号整数),Java 中有哪些替代方案?
# 2.3 字符类型
# 2.3.1 字符类型说明
作用:字符型变量用于存储单个字符
Java 的 char 类型占 2 字节,使用 Unicode 编码,可以存储中文字符。
char ch1 = 'A';
char ch2 = '中'; // 可以存储中文
char ch3 = 65; // 可以用 Unicode 编码值赋值,等价于 'A'
char ch4 = '\u0041'; // 可以用 Unicode 转义,等价于 'A'
System.out.println(ch1); // A
System.out.println(ch2); // 中
System.out.println((int) ch1); // 65
2
3
4
5
6
7
8
对比 C++:C++ 的 char 只有 1 字节(ASCII),要存中文需要 wchar_t。Java 的 char 天然支持 Unicode。
# 2.3.2 字符与ASCII
char ch = 'a';
System.out.println((int) ch); // 97,查看字符对应的编码值
// 字符可以参与运算
char c = 'A';
System.out.println(c + 1); // 66(int 类型结果)
System.out.println((char)(c + 1)); // B
2
3
4
5
6
7
# 2.3.3 转义字符
| 转义字符 | 含义 |
|---|---|
\n | 换行 |
\t | 制表符 |
\\ | 反斜杠 |
\' | 单引号 |
\" | 双引号 |
\uXXXX | Unicode 字符 |
# 2.3.4 综合案例:字符编码查看器
编写一个程序,输入一个字符串,逐字符显示其 Unicode 编码、字符分类(字母/数字/中文/特殊字符),并演示大小写转换。
import java.util.Scanner;
public class CharEncoder {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入一个字符串:");
String input = sc.nextLine();
System.out.println("\n===== 字符编码详情 =====");
System.out.printf("%-6s %-8s %-10s %-12s%n", "字符", "Unicode", "十六进制", "分类");
System.out.println("-".repeat(40));
int letterCount = 0, digitCount = 0, chineseCount = 0, otherCount = 0;
for (int i = 0; i < input.length(); i++) {
char ch = input.charAt(i);
String category;
if (Character.isUpperCase(ch)) {
category = "大写字母";
letterCount++;
} else if (Character.isLowerCase(ch)) {
category = "小写字母";
letterCount++;
} else if (Character.isDigit(ch)) {
category = "数字";
digitCount++;
} else if (ch >= '\u4e00' && ch <= '\u9fff') {
category = "中文";
chineseCount++;
} else {
category = "特殊字符";
otherCount++;
}
System.out.printf("%-6c %-8d \\u%-8s %-12s%n", ch, (int) ch,
String.format("%04X", (int) ch), category);
}
// 统计信息
System.out.println("\n===== 统计信息 =====");
System.out.println("字母: " + letterCount + ", 数字: " + digitCount
+ ", 中文: " + chineseCount + ", 其他: " + otherCount);
// 大小写转换演示
System.out.println("\n===== 大小写转换 =====");
StringBuilder upper = new StringBuilder();
StringBuilder lower = new StringBuilder();
for (char ch : input.toCharArray()) {
upper.append(Character.toUpperCase(ch));
lower.append(Character.toLowerCase(ch));
}
System.out.println("全部大写: " + upper);
System.out.println("全部小写: " + lower);
// 转义字符演示
System.out.println("\n===== 转义字符演示 =====");
System.out.println("换行符: Hello\\nWorld → Hello\nWorld");
System.out.println("制表符: A\\tB → A\tB");
System.out.println("反斜杠: \\\\ → \\");
sc.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# 2.3.5 训练题
代码题:请写出以下代码的输出:
char c1 = 'A'; char c2 = 'a'; System.out.println(c1 + c2); System.out.println("" + c1 + c2);1
2
3
4实践题:编写代码,将用户输入的一个小写字母转换为大写字母(提示:利用 ASCII 码差值)。
思考题:Java 的
char是 2 字节(UTF-16),可以存储大部分中文字符。但对于一些生僻汉字(如 emoji 表情),一个char能存下吗?Java 如何处理这种情况?
# 2.4 浮点类型
作用:用于表示小数
# 2.4.1 浮点类型列表
| 数据类型 | 占用空间 | 精度(有效数字) | 默认值 |
|---|---|---|---|
float | 4字节 | 6-7 位 | 0.0f |
double | 8字节 | 15-16 位 | 0.0 |
float f = 3.14f; // float 必须加 f 后缀
double d = 3.14; // 默认是 double 类型
double d2 = 3.14d; // 加 d 后缀(可选)
System.out.println(f); // 3.14
System.out.println(d); // 3.14
2
3
4
5
6
注意:浮点字面量默认是
double类型,赋值给float变量需要加f后缀,否则编译报错。
# 2.4.2 浮点精度问题
浮点数在计算时可能会产生精度损失:
System.out.println(0.1 + 0.2); // 0.30000000000000004,不是 0.3!
System.out.println(1.0 - 0.9); // 0.09999999999999998
// 不要用 == 比较浮点数
double a = 0.1 + 0.2;
double b = 0.3;
System.out.println(a == b); // false!
// 正确做法:使用误差范围比较
System.out.println(Math.abs(a - b) < 1e-9); // true
2
3
4
5
6
7
8
9
# 2.4.3 BigDecimal精确计算
对于需要精确计算的场景(如金融计算),使用 BigDecimal:
import java.math.BigDecimal;
BigDecimal a = new BigDecimal("0.1");
BigDecimal b = new BigDecimal("0.2");
BigDecimal result = a.add(b);
System.out.println(result); // 0.3(精确结果)
// 注意:必须用字符串构造 BigDecimal
// new BigDecimal(0.1) 仍然有精度问题!
2
3
4
5
6
7
8
9
对比 C++:C++ 标准库没有 BigDecimal,需要自己实现或使用第三方库。Java 标准库就内置了。
# 2.4.4 综合案例:精确价格比较器
编写一个程序,演示浮点数精度问题、BigDecimal 正确用法和错误用法的对比,以及特殊浮点值的处理。
import java.math.BigDecimal;
import java.math.RoundingMode;
public class PrecisionCompare {
public static void main(String[] args) {
// 精度问题演示
System.out.println("===== 浮点精度陷阱 =====");
double a = 0.1 + 0.2;
double b = 0.3;
System.out.println("0.1 + 0.2 = " + a); // 0.30000000000000004
System.out.println("0.3 = " + b);
System.out.println("== 比较: " + (a == b)); // false
System.out.println("误差比较: " + (Math.abs(a - b) < 1e-9)); // true
// BigDecimal 正确 vs 错误用法
System.out.println("\n===== BigDecimal 正确 vs 错误 =====");
BigDecimal wrong = new BigDecimal(0.1); // 用double构造(不精确)
BigDecimal right = new BigDecimal("0.1"); // 用字符串构造(精确)
System.out.println("new BigDecimal(0.1) = " + wrong);
System.out.println("new BigDecimal(\"0.1\") = " + right);
// 模拟购物车价格计算
System.out.println("\n===== 购物车精确计算 =====");
String[][] items = {
{"Java编程思想", "89.50", "1"},
{"机械键盘", "299.99", "1"},
{"USB数据线", "9.90", "3"},
};
BigDecimal total = BigDecimal.ZERO;
for (String[] item : items) {
BigDecimal price = new BigDecimal(item[1]);
BigDecimal qty = new BigDecimal(item[2]);
BigDecimal subtotal = price.multiply(qty);
total = total.add(subtotal);
System.out.printf(" %s: ¥%s × %s = ¥%s%n", item[0], item[1], item[2], subtotal);
}
System.out.println(" 小计: ¥" + total);
// 打折和四舍五入
BigDecimal discount = new BigDecimal("0.88");
BigDecimal discounted = total.multiply(discount).setScale(2, RoundingMode.HALF_UP);
System.out.println(" 88折后: ¥" + discounted);
// 特殊浮点值
System.out.println("\n===== 特殊浮点值 =====");
System.out.println("1.0 / 0 = " + (1.0 / 0)); // Infinity
System.out.println("-1.0 / 0 = " + (-1.0 / 0)); // -Infinity
System.out.println("0.0 / 0 = " + (0.0 / 0)); // NaN
System.out.println("NaN == NaN: " + (Double.NaN == Double.NaN)); // false
System.out.println("isNaN检测: " + Double.isNaN(0.0 / 0)); // true
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 2.4.5 训练题
代码题:请预测以下代码的输出结果:
System.out.println(1.0 / 0); System.out.println(0.0 / 0); System.out.println(Double.isNaN(0.0 / 0));1
2
3实践题:使用
BigDecimal计算10.00 元 × 0.7 折扣 - 3.50 元优惠券的最终价格,结果保留两位小数。思考题:
new BigDecimal(0.1)和new BigDecimal("0.1")的结果不同,为什么?在使用 BigDecimal 时有哪些常见的陷阱?
# 2.5 布尔类型
# 2.5.1 布尔类型使用
作用:布尔数据类型代表真或假的值
boolean flag1 = true;
boolean flag2 = false;
boolean result = (10 > 5); // true
System.out.println(flag1); // true
System.out.println(result); // true
2
3
4
5
6
# 2.5.2 布尔与C++的区别
Java 的 boolean 和 C++ 的 bool 有一个重要区别:
// Java:boolean 不能和 int 互转
boolean b = true;
// int i = b; // 编译错误!
// boolean b2 = 1; // 编译错误!
// if (1) {} // 编译错误!if 条件必须是 boolean
// C++:bool 可以和 int 互转
// bool b = true;
// int i = b; // OK,i = 1
// bool b2 = 1; // OK,b2 = true
// if (1) {} // OK
2
3
4
5
6
7
8
9
10
11
Java 更严格,布尔类型就是布尔类型,不能与整数混用,这样可以避免很多 C++ 中常见的错误(如 if (a = 1) 写成赋值)。
# 2.5.3 综合案例:条件判断验证器
编写一个程序,演示布尔类型在条件判断、逻辑运算中的正确使用方式,以及 Java 和 C++ 在布尔类型上的关键差异。
public class BooleanValidator {
public static void main(String[] args) {
// 布尔类型基本使用
System.out.println("===== 布尔类型基本使用 =====");
boolean isJava = true;
boolean isCpp = false;
System.out.println("isJava: " + isJava);
System.out.println("isCpp: " + isCpp);
// 比较运算产生布尔值
int score = 85;
boolean passed = score >= 60;
boolean excellent = score >= 90;
System.out.println("成绩 " + score + " 分, 及格: " + passed + ", 优秀: " + excellent);
// 逻辑运算
System.out.println("\n===== 逻辑运算 =====");
boolean hasTicket = true;
boolean hasID = true;
boolean isVIP = false;
boolean canEnter = (hasTicket && hasID) || isVIP;
System.out.println("有票: " + hasTicket + ", 有证件: " + hasID + ", VIP: " + isVIP);
System.out.println("能否入场: " + canEnter);
// 短路求值演示
System.out.println("\n===== 短路求值 =====");
int x = 10;
boolean result = (x > 5) || checkValue(x); // 短路,不会调用checkValue
System.out.println("短路或(||): x>5为true, checkValue未被调用, result=" + result);
result = (x < 5) && checkValue(x); // 短路,不会调用checkValue
System.out.println("短路与(&&): x<5为false, checkValue未被调用, result=" + result);
// Java vs C++ 差异
System.out.println("\n===== Java vs C++ 差异 =====");
System.out.println("Java: boolean不能与int互转");
System.out.println(" boolean b = true;");
System.out.println(" // int i = b; → 编译错误!");
System.out.println(" // boolean b2 = 1; → 编译错误!");
System.out.println(" // if (1) {} → 编译错误!");
System.out.println("C++: bool可以与int互转");
System.out.println(" bool b = true; int i = b; → OK, i=1");
System.out.println(" if (1) {} → OK");
// 布尔类型的包装类
System.out.println("\n===== Boolean包装类 =====");
Boolean flag1 = Boolean.valueOf(true);
Boolean flag2 = Boolean.valueOf("true");
Boolean flag3 = Boolean.valueOf("yes"); // 非"true"字符串都是false
System.out.println("Boolean.valueOf(true): " + flag1);
System.out.println("Boolean.valueOf(\"true\"): " + flag2);
System.out.println("Boolean.valueOf(\"yes\"): " + flag3); // false
}
static boolean checkValue(int val) {
System.out.println(" checkValue被调用了!");
return val > 0;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# 2.5.4 训练题
判断题:
boolean b = 1;在 Java 中可以编译通过。( )代码题:以下代码在 Java 中能编译通过吗?如果不能,说明原因:
int x = 10; if (x) { System.out.println("true"); }1
2
3
4思考题:Java 的
boolean在 JVM 规范中并没有规定固定大小。请查阅资料,说明boolean类型在 JVM 中实际占用多少字节?为什么boolean[]数组中每个元素占 1 字节?
# 2.6 变量和常量
# 2.6.1 变量
作用:给一段指定的内存空间起名,方便操作这段内存。
语法:数据类型 变量名 = 初始值;
public class Main {
public static void main(String[] args) {
int a = 10;
System.out.println("a = " + a);
// Java 10 引入 var 关键字(类似 C++ 的 auto)
var name = "张三"; // 编译器自动推断为 String
var count = 100; // 编译器自动推断为 int
}
}
2
3
4
5
6
7
8
9
10
# 2.6.2 常量
作用:用于记录程序中不可更改的数据
Java 使用 final 关键字定义常量:
public class Main {
// 类级别常量(通常配合 static 使用)
static final double PI = 3.14159;
static final int MAX_SIZE = 100;
public static void main(String[] args) {
// 局部常量
final int month = 12;
System.out.println("一年有 " + month + " 个月");
// month = 24; // 编译错误,常量不可修改
}
}
2
3
4
5
6
7
8
9
10
11
12
对比 C++:C++ 使用 const 或 #define 定义常量,Java 使用 final。Java 没有 #define(Java 没有预处理器)。
# 2.6.3 变量作用域
public class Main {
static int classVar = 10; // 类变量(静态变量)
int instanceVar = 20; // 实例变量
public void method() {
int localVar = 30; // 局部变量
System.out.println(localVar);
}
public static void main(String[] args) {
// 局部变量必须先初始化才能使用
int a;
// System.out.println(a); // 编译错误:变量 a 可能未初始化
a = 10;
System.out.println(a); // OK
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
对比 C++:C++ 的局部变量不初始化时值是随机的(未定义行为),Java 编译器会强制要求局部变量在使用前初始化。
# 2.6.4 综合案例:个人账户信息管理
编写一个程序,综合运用变量、常量、var 推断和作用域知识,模拟一个简单的个人账户系统。
public class AccountManager {
// 类级别常量
static final double TAX_RATE = 0.03; // 税率 3%
static final int MAX_NICKNAME_LENGTH = 10; // 昵称最大长度
static final String CURRENCY = "CNY"; // 货币单位
// 实例变量(有默认值)
String name;
double balance;
int level;
public AccountManager(String name, double balance) {
this.name = name;
this.balance = balance;
this.level = 1; // 默认等级
}
public void deposit(double amount) {
// 局部变量必须初始化
final double MAX_DEPOSIT = 50000.0; // 局部常量
if (amount > MAX_DEPOSIT) {
System.out.println("单笔存款不能超过 " + MAX_DEPOSIT + " 元");
return;
}
balance += amount;
System.out.printf("存款 %.2f 元, 余额: %.2f %s%n", amount, balance, CURRENCY);
}
public void withdraw(double amount) {
// 使用 var 推断类型(Java 10+)
var fee = amount * TAX_RATE; // 编译器推断为 double
var totalDeduct = amount + fee;
if (totalDeduct > balance) {
System.out.println("余额不足! 需要: " + totalDeduct + ", 当前: " + balance);
return;
}
balance -= totalDeduct;
System.out.printf("取款 %.2f 元, 手续费 %.2f 元, 余额: %.2f %s%n",
amount, fee, balance, CURRENCY);
}
public void showInfo() {
System.out.println("===== 账户信息 =====");
System.out.println("姓名: " + name);
System.out.printf("余额: %.2f %s%n", balance, CURRENCY);
System.out.println("等级: " + level);
System.out.println("税率: " + (TAX_RATE * 100) + "%");
}
public static void main(String[] args) {
// 变量声明和初始化
var account = new AccountManager("张三", 10000.0);
account.showInfo();
System.out.println();
account.deposit(5000);
account.withdraw(2000);
System.out.println();
account.showInfo();
// 作用域演示
System.out.println("\n===== 作用域演示 =====");
{
int blockVar = 100; // 块级作用域变量
System.out.println("块内 blockVar = " + blockVar);
}
// System.out.println(blockVar); // 编译错误:超出作用域
for (int i = 0; i < 3; i++) {
var msg = "循环 " + i; // 每次循环创建新的局部变量
System.out.println(msg);
}
// System.out.println(i); // 编译错误:i 超出作用域
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# 2.6.5 训练题
改错题:以下代码有什么问题?
public class Test { public static void main(String[] args) { final int MAX; MAX = 100; MAX = 200; System.out.println(MAX); } }1
2
3
4
5
6
7
8填空题:Java 中类变量(static)和实例变量有默认值,但 ___ 变量必须在使用前手动初始化。
实践题:编写一个程序,定义以下变量并输出:一个
int类型的局部变量、一个final常量、一个使用var推断类型的变量。思考题:Java 使用
final定义常量,C++ 使用const。Java 中有没有类似 C++constexpr(编译期常量)的概念?static final和final有什么区别?
# 2.7 类型转换
# 2.7.1 自动类型转换
自动类型转换(隐式转换):小类型自动转为大类型,不会丢失数据。
转换路线:byte → short → int → long → float → double
byte b = 10;
int i = b; // 自动转换,byte → int
long l = i; // 自动转换,int → long
double d = l; // 自动转换,long → double
char c = 'A';
int ci = c; // 自动转换,char → int(值为65)
2
3
4
5
6
7
# 2.7.2 强制类型转换
强制类型转换(显式转换):大类型转为小类型,可能丢失数据。
double d = 3.99;
int i = (int) d; // 强制转换,i = 3(截断小数部分)
int big = 130;
byte b = (byte) big; // 强制转换,b = -126(溢出)
System.out.println(i); // 3
System.out.println(b); // -126
2
3
4
5
6
7
8
# 2.7.3 类型转换注意
boolean不参与任何类型转换。byte、short、char在运算时自动提升为int。- 强制转换可能导致精度丢失或数据溢出。
byte a = 10;
byte b = 20;
// byte c = a + b; // 编译错误!a + b 结果是 int
byte c = (byte)(a + b); // 需要强制转换
int d = a + b; // 或者用 int 接收
2
3
4
5
# 2.7.4 综合案例:类型转换安全检查器
编写一个程序,输入一个数值,检测其在各种类型转换时是否安全(是否会丢失精度或溢出),并演示自动转换和强制转换的区别。
import java.util.Scanner;
public class TypeCastChecker {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.print("请输入一个数值(支持小数):");
double input = sc.nextDouble();
System.out.println("\n===== 类型转换安全检查 =====");
// double → float
float asFloat = (float) input;
boolean floatSafe = (double) asFloat == input;
System.out.printf("double→float: %.15f → %.7f %s%n", input, asFloat,
floatSafe ? "[安全]" : "[精度丢失!]");
// double → long
long asLong = (long) input;
boolean longSafe = (input == Math.floor(input)) && input >= Long.MIN_VALUE && input <= Long.MAX_VALUE;
System.out.printf("double→long: %f → %d %s%n", input, asLong,
longSafe ? "[安全]" : "[截断小数或溢出!]");
// double → int
boolean intSafe = (input == Math.floor(input)) && input >= Integer.MIN_VALUE && input <= Integer.MAX_VALUE;
int asInt = (int) input;
System.out.printf("double→int: %f → %d %s%n", input, asInt,
intSafe ? "[安全]" : "[截断小数或溢出!]");
// double → short
boolean shortSafe = (input == Math.floor(input)) && input >= Short.MIN_VALUE && input <= Short.MAX_VALUE;
short asShort = (short) asInt;
System.out.printf("double→short: %f → %d %s%n", input, asShort,
shortSafe ? "[安全]" : "[数据丢失!]");
// double → byte
boolean byteSafe = (input == Math.floor(input)) && input >= Byte.MIN_VALUE && input <= Byte.MAX_VALUE;
byte asByte = (byte) asInt;
System.out.printf("double→byte: %f → %d %s%n", input, asByte,
byteSafe ? "[安全]" : "[数据丢失!]");
// 自动类型转换链演示
System.out.println("\n===== 自动转换链 =====");
byte b = 42;
short s = b; // byte → short
int i = s; // short → int
long l = i; // int → long
float f = l; // long → float (可能丢精度!)
double d = f; // float → double
System.out.printf("byte(%d) → short(%d) → int(%d) → long(%d) → float(%f) → double(%f)%n",
b, s, i, l, f, d);
// long → float 精度丢失演示
System.out.println("\n===== long→float 精度丢失 =====");
long bigLong = 123456789123456789L;
float bigFloat = bigLong; // 自动转换但丢精度
System.out.println("long值: " + bigLong);
System.out.println("转float: " + bigFloat);
System.out.println("转回long: " + (long) bigFloat);
System.out.println("精度丢失: " + (bigLong != (long) bigFloat));
// byte运算自动提升为int
System.out.println("\n===== byte运算提升 =====");
byte x = 10, y = 20;
// byte z = x + y; // 编译错误! x+y结果是int
int z = x + y; // 用int接收
byte z2 = (byte)(x + y); // 或强制转换
System.out.println("byte + byte → int: " + z);
System.out.println("强制转回byte: " + z2);
sc.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# 2.7.5 训练题
代码题:请预测以下代码的输出:
byte a = 10, b = 20; // byte c = a + b; // 编译错误还是正确? int c = a + b; System.out.println(c); short s = 100; s += 200; // 编译错误还是正确? System.out.println(s);1
2
3
4
5
6
7
8实践题:编写一个温度转换程序,将用户输入的华氏温度(double)转换为摄氏温度(double),然后再强制转换为 int 输出(公式:℃ = (℉ - 32) × 5 / 9)。
思考题:
long l = 100;和float f = 100L;都能编译通过(自动类型转换),但long是 8 字节而float只有 4 字节,为什么小容量的float能接收大容量的long?会有精度损失吗?
# 2.8 包装类
# 2.8.1 包装类介绍
Java 为每种基本类型提供了对应的包装类(Wrapper Class),使基本类型可以作为对象使用。
| 基本类型 | 包装类 |
|---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
为什么需要包装类?因为集合(如 ArrayList)只能存储对象,不能存储基本类型。
// ArrayList 不能直接存 int
// ArrayList<int> list; // 编译错误!
ArrayList<Integer> list = new ArrayList<>(); // 用 Integer 包装类
list.add(10); // 自动装箱:int → Integer
2
3
4
# 2.8.2 自动装箱拆箱
Java 5 引入了自动装箱和拆箱:
自动装箱拆箱的原理:自动装箱实际上是编译器在编译时自动插入了 Integer.valueOf() 调用,自动拆箱则插入了 intValue() 调用。这是编译器层面的语法糖,字节码中并不存在自动装箱/拆箱的指令。需要注意的是,频繁的装箱/拆箱会产生大量临时 Integer 对象,影响性能。在循环中应优先使用基本类型。
// 自动装箱:基本类型 → 包装类
Integer a = 10; // 等价于 Integer a = Integer.valueOf(10);
// 自动拆箱:包装类 → 基本类型
int b = a; // 等价于 int b = a.intValue();
// 在运算中自动拆箱
Integer x = 10;
Integer y = 20;
int sum = x + y; // x 和 y 自动拆箱后相加
2
3
4
5
6
7
8
9
10
# 2.8.3 包装类缓存
Java 对部分包装类做了缓存优化:
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true(缓存范围内,同一个对象)
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false(超出缓存范围,不同对象)
System.out.println(c.equals(d)); // true(值相等)
2
3
4
5
6
7
8
注意:
Integer缓存了 -128 到 127 的值。比较包装类对象的值应该用equals(),不要用==。
# 2.8.4 常用方法
// 字符串转数值
int i = Integer.parseInt("123");
double d = Double.parseDouble("3.14");
long l = Long.parseLong("123456789");
// 数值转字符串
String s1 = Integer.toString(123);
String s2 = String.valueOf(123);
String s3 = 123 + ""; // 简便写法
// 获取最大值最小值
System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Integer.MIN_VALUE); // -2147483648
2
3
4
5
6
7
8
9
10
11
12
13
# 2.8.5 综合案例:包装类工具箱
编写一个程序,综合演示包装类的创建方式、自动装箱拆箱、缓存机制、字符串互转以及性能对比。
public class WrapperToolbox {
public static void main(String[] args) {
// 1. 创建包装类对象的多种方式
System.out.println("===== 创建方式对比 =====");
Integer a1 = Integer.valueOf(100); // 推荐: valueOf(利用缓存)
Integer a2 = Integer.valueOf("100"); // 从字符串创建
int a3 = Integer.parseInt("100"); // 直接解析为基本类型
System.out.println("valueOf(100): " + a1);
System.out.println("valueOf(\"100\"): " + a2);
System.out.println("parseInt(\"100\"): " + a3);
// 2. 缓存机制深度测试
System.out.println("\n===== 缓存机制测试 =====");
System.out.println("--- Integer 缓存 [-128, 127] ---");
for (int val : new int[]{-128, -1, 0, 1, 127, 128, 256}) {
Integer x = Integer.valueOf(val);
Integer y = Integer.valueOf(val);
System.out.printf(" valueOf(%4d): == %s, equals %s%n",
val, x == y, x.equals(y));
}
System.out.println("--- Boolean 缓存 ---");
Boolean t1 = Boolean.valueOf(true);
Boolean t2 = Boolean.valueOf(true);
System.out.println(" Boolean.valueOf(true): == " + (t1 == t2)); // true
System.out.println("--- Character 缓存 [0, 127] ---");
Character c1 = Character.valueOf('A'); // 65, 在缓存内
Character c2 = Character.valueOf('A');
System.out.println(" valueOf('A'): == " + (c1 == c2)); // true
// 3. 自动装箱拆箱与null安全
System.out.println("\n===== 自动装箱拆箱与null陷阱 =====");
Integer num = null;
try {
int value = num; // 自动拆箱:null.intValue() → NPE!
} catch (NullPointerException e) {
System.out.println("null自动拆箱抛出NullPointerException!");
}
// 安全写法
int safeValue = (num != null) ? num : 0;
System.out.println("安全拆箱: " + safeValue);
// 4. 字符串与数值互转
System.out.println("\n===== 字符串互转 =====");
String numStr = "12345";
int intVal = Integer.parseInt(numStr);
long longVal = Long.parseLong(numStr);
double doubleVal = Double.parseDouble("3.14159");
System.out.println("字符串→int: " + intVal);
System.out.println("字符串→long: " + longVal);
System.out.println("字符串→double: " + doubleVal);
System.out.println("int→字符串: " + Integer.toString(intVal));
System.out.println("int→二进制: " + Integer.toBinaryString(intVal));
System.out.println("int→十六进制: " + Integer.toHexString(intVal));
// 5. 性能对比:基本类型 vs 包装类
System.out.println("\n===== 性能对比 =====");
long start, end;
final int COUNT = 10_000_000;
// 使用基本类型
start = System.currentTimeMillis();
long sum1 = 0;
for (int i = 0; i < COUNT; i++) {
sum1 += i;
}
end = System.currentTimeMillis();
System.out.println("基本类型 long 累加: " + (end - start) + "ms, sum=" + sum1);
// 使用包装类(大量自动装箱拆箱)
start = System.currentTimeMillis();
Long sum2 = 0L;
for (int i = 0; i < COUNT; i++) {
sum2 += i; // 每次都拆箱→运算→装箱
}
end = System.currentTimeMillis();
System.out.println("包装类 Long 累加: " + (end - start) + "ms, sum=" + sum2);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 2.8.6 训练题
代码题:请预测输出并解释原因:
Integer a = new Integer(127); Integer b = new Integer(127); System.out.println(a == b); System.out.println(a.equals(b)); Integer c = Integer.valueOf(127); Integer d = Integer.valueOf(127); System.out.println(c == d);1
2
3
4
5
6
7
8实践题:编写一个方法,接受一个
String参数,将其转换为int。要求处理以下异常情况:空字符串、非数字字符串、超出 int 范围的数字。思考题:自动装箱拆箱虽然方便,但在性能敏感的场景(如大循环中)可能带来问题。请分析以下代码的性能问题并给出优化方案:
Long sum = 0L; for (int i = 0; i < 1000000; i++) { sum += i; // 这里发生了什么? }1
2
3
4
# 2.8.7 类型转换案例
public class Main {
public static void main(String[] args) {
// 自动类型转换
int a = 10;
double b = a;
System.out.println("b = " + b); // 10.0
// 强制类型转换
double c = 3.99;
int d = (int) c;
System.out.println("d = " + d); // 3
// 自动装箱拆箱
Integer e = 100; // 自动装箱
int f = e; // 自动拆箱
System.out.println("f = " + f); // 100
// 包装类比较
Integer g = 127;
Integer h = 127;
System.out.println("g == h: " + (g == h)); // true
System.out.println("g.equals(h): " + g.equals(h)); // true
Integer i = 128;
Integer j = 128;
System.out.println("i == j: " + (i == j)); // false
System.out.println("i.equals(j): " + i.equals(j)); // true
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 2.8.8 键盘数据输入
作用:用于从键盘获取数据
关键类:Scanner
语法: Scanner sc = new Scanner(System.in);
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 整型输入
System.out.print("请输入整数:");
int a = sc.nextInt();
System.out.println("a = " + a);
// 浮点型输入
System.out.print("请输入小数:");
double d = sc.nextDouble();
System.out.println("d = " + d);
// 字符串输入
System.out.print("请输入字符串:");
String str = sc.next();
System.out.println("str = " + str);
// 读取一整行
sc.nextLine(); // 消耗换行符
System.out.print("请输入一行文字:");
String line = sc.nextLine();
System.out.println("line = " + line);
// 布尔输入
System.out.print("请输入布尔值(true/false):");
boolean flag = sc.nextBoolean();
System.out.println("flag = " + flag);
sc.close();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
对比 C++:C++ 用 cin >> 变量 读取输入,Java 用 Scanner 类的各种 nextXxx() 方法。