类和对象
# 07.类和对象
# 目录介绍
# 7.1 类的介绍
# 7.1.1 类的含义
类是对象的模板(蓝图),封装了数据(属性/字段)和行为(方法)。对象是类的实例。
类(Class):设计图纸 → 对象(Object):根据图纸造出的房子
Account 类(账户模板) → 张三的账户、李四的账户(具体的账户对象)
2
对比 C++:概念完全一致。区别在于 Java 中所有对象都在堆上创建(通过 new),没有 C++ 中在栈上创建对象的方式。
对象创建的底层原理:执行 new 指令时,JVM 完成以下步骤:①类加载检查——检查该类是否已被加载、解析和初始化;②分配内存——在堆上为对象分配空间,使用指针碰撞(内存规整时)或空闲列表(内存不规整时)算法;③内存清零——将分配的内存空间初始化为零值(这就是成员变量有默认值的原因);④设置对象头——存储 Mark Word(哈希码、GC 分代年龄、锁状态)和类型指针;⑤执行 <init> 方法——即构造方法,完成程序员指定的初始化。
# 7.1.2 面向对象三大特性
- 封装:将数据和操作数据的方法绑定在一起,隐藏内部实现细节。
- 继承:子类继承父类的属性和方法,实现代码复用。
- 多态:同一个方法在不同对象上有不同的表现形式。
# 7.1.3 综合案例:面向对象概念演示
编写一个程序,用一个简单的"宠物"示例演示类、对象和面向对象三大特性的基本概念。
public class OopConceptDemo {
// 类 = 蓝图/模板
static class Pet {
private String name; // 封装:属性私有
private String type;
private int energy;
Pet(String name, String type) {
this.name = name;
this.type = type;
this.energy = 100;
}
// 封装:通过方法访问
public String getName() { return name; }
public void play() {
energy -= 20;
System.out.println(name + "(" + type + ")在玩耍, 体力: " + energy);
}
public void feed() {
energy = Math.min(energy + 30, 100);
System.out.println(name + "吃饱了, 体力: " + energy);
}
public void status() {
String mood = energy >= 80 ? "开心" : energy >= 50 ? "一般" : "疲惫";
System.out.println(name + " - " + type + " | 体力:" + energy + " | 心情:" + mood);
}
}
public static void main(String[] args) {
// 对象 = 类的实例
Pet cat = new Pet("小花", "猫");
Pet dog = new Pet("旺财", "狗");
System.out.println("===== 宠物状态 =====");
cat.status();
dog.status();
System.out.println("\n===== 互动 =====");
cat.play();
cat.play();
cat.feed();
dog.play();
System.out.println("\n===== 最终状态 =====");
cat.status();
dog.status();
// 两个对象互相独立
System.out.println("\n===== 对象独立性 =====");
System.out.println("cat和dog是同一个对象吗? " + (cat == dog));
}
}
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
# 7.1.4 训练题
填空题:类是对象的 ___(蓝图),对象是类的 ___。
选择题:以下关于 Java 对象创建的说法,正确的是?
- A. Java 对象可以在栈上创建 B. 所有 Java 对象都通过
new在堆上创建 C. Java 对象需要手动释放 D. Java 的new和 C++ 的new用法完全一致
- A. Java 对象可以在栈上创建 B. 所有 Java 对象都通过
思考题:面向对象的三大特性(封装、继承、多态)之间有什么关系?为什么说封装是基础,多态是目标?请结合实际例子说明。
# 7.2 类定义和对象
# 7.2.1 类的定义
public class Account {
// 成员变量(属性)
private String name;
private double balance;
// 构造方法
public Account(String name, double balance) {
this.name = name;
this.balance = balance;
}
// 成员方法(行为)
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println(name + " 存入 " + amount + ",余额:" + balance);
}
}
public void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
System.out.println(name + " 取出 " + amount + ",余额:" + balance);
}
}
// getter 和 setter
public String getName() { return name; }
public double getBalance() { return balance; }
}
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
# 7.2.2 创建对象
// 使用 new 创建对象
Account acc1 = new Account("张三", 1000);
Account acc2 = new Account("李四", 2000);
2
3
对比 C++:
// C++ 可以在栈上创建
Account acc1("张三", 1000);
// C++ 也可以在堆上创建
Account* acc2 = new Account("李四", 2000);
delete acc2; // 需要手动释放
2
3
4
5
Java 不需要 delete,GC 会自动回收不再使用的对象。
# 7.2.3 访问类成员
Account acc = new Account("张三", 1000);
acc.deposit(500);
acc.withdraw(200);
System.out.println(acc.getName()); // 张三
System.out.println(acc.getBalance()); // 1300
2
3
4
5
# 7.2.4 案例实践
public class Main {
public static void main(String[] args) {
Account acc1 = new Account("张三", 1000);
Account acc2 = new Account("李四", 2000);
acc1.deposit(500); // 张三 存入 500,余额:1500
acc2.withdraw(800); // 李四 取出 800,余额:1200
System.out.println(acc1.getName() + " 余额:" + acc1.getBalance());
System.out.println(acc2.getName() + " 余额:" + acc2.getBalance());
}
}
2
3
4
5
6
7
8
9
10
11
12
# 7.2.5 综合案例:银行账户系统
编写一个完整的银行账户类,综合运用类定义、构造方法、实例方法、封装和对象交互。
public class BankAccountDemo {
static class BankAccount {
private String owner;
private String accountId;
private double balance;
private static int nextId = 1001;
BankAccount(String owner, double initBalance) {
this.owner = owner;
this.accountId = "ACC" + nextId++;
this.balance = Math.max(initBalance, 0);
}
public boolean deposit(double amount) {
if (amount <= 0) return false;
balance += amount;
System.out.printf(" [%s] %s 存入 %.2f, 余额: %.2f%n", accountId, owner, amount, balance);
return true;
}
public boolean withdraw(double amount) {
if (amount <= 0 || amount > balance) {
System.out.printf(" [%s] %s 取款失败! 余额不足%n", accountId, owner);
return false;
}
balance -= amount;
System.out.printf(" [%s] %s 取出 %.2f, 余额: %.2f%n", accountId, owner, amount, balance);
return true;
}
public boolean transfer(BankAccount target, double amount) {
if (this.withdraw(amount)) {
target.deposit(amount);
System.out.printf(" → %s 向 %s 转账 %.2f 成功%n", owner, target.owner, amount);
return true;
}
return false;
}
public String toString() {
return String.format("[%s] %s: ¥%.2f", accountId, owner, balance);
}
}
public static void main(String[] args) {
BankAccount acc1 = new BankAccount("张三", 5000);
BankAccount acc2 = new BankAccount("李四", 3000);
System.out.println("===== 初始状态 =====");
System.out.println(acc1);
System.out.println(acc2);
System.out.println("\n===== 操作 =====");
acc1.deposit(2000);
acc2.withdraw(1000);
acc1.transfer(acc2, 3000);
System.out.println("\n===== 最终状态 =====");
System.out.println(acc1);
System.out.println(acc2);
}
}
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
# 7.2.6 训练题
实践题:定义一个
Student类,包含name(String)、age(int)、score(double)三个属性,提供有参构造方法和一个introduce()方法输出学生信息。在main中创建 3 个学生对象并调用introduce()。代码题:以下代码的输出是什么?
Account a1 = new Account("张三", 100); Account a2 = a1; a2.deposit(200); System.out.println(a1.getBalance());1
2
3
4思考题:
Account a1 = new Account("张三", 100);这行代码在内存中发生了什么?栈和堆分别存了什么?如果a1 = null,原来的对象会怎样?
# 7.3 类成员属性
# 7.3.1 成员变量
public class Account {
// 实例变量:每个对象各自拥有
String name;
double balance;
// 类变量(静态变量):所有对象共享
static int totalCount = 0;
}
2
3
4
5
6
7
8
成员变量的默认值:
| 类型 | 默认值 |
|---|---|
int、long 等整数类型 | 0 |
float、double | 0.0 |
boolean | false |
char | '\u0000' |
| 引用类型 | null |
# 7.3.2 成员方法
public class Account {
private String name;
private double balance;
// 实例方法
public void deposit(double amount) {
balance += amount;
}
// 静态方法
public static void printWelcome() {
System.out.println("欢迎使用银行系统");
}
}
// 调用
Account acc = new Account();
acc.deposit(100); // 实例方法通过对象调用
Account.printWelcome(); // 静态方法通过类名调用
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 7.3.3 访问权限
public class Account {
public String name; // 公共
protected double balance; // 受保护
int id; // 默认(包访问)
private String password; // 私有
}
2
3
4
5
6
# 7.3.4 封装思想
封装的核心思想:属性私有化,方法公开化。
public class Account {
// 属性私有
private String name;
private double balance;
// 通过公开的方法访问
public String getName() {
return name;
}
public void setName(String name) {
// 可以在 setter 中加入验证逻辑
if (name != null && !name.isEmpty()) {
this.name = name;
}
}
public double getBalance() {
return balance;
}
// balance 只提供 getter,不提供 setter(只读)
// 余额只能通过 deposit 和 withdraw 修改
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
}
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
# 7.3.5 综合案例:学生信息封装
编写一个 Student 类,综合运用属性封装(getter/setter)、数据验证和 this 关键字。
public class StudentEncapsulation {
static class Student {
private String name;
private int age;
private double score;
public Student(String name, int age, double score) {
setName(name);
setAge(age);
setScore(score);
}
public String getName() { return name; }
public void setName(String name) {
if (name == null || name.isBlank()) throw new IllegalArgumentException("姓名不能为空");
this.name = name;
}
public int getAge() { return age; }
public void setAge(int age) {
if (age < 1 || age > 150) throw new IllegalArgumentException("年龄无效: " + age);
this.age = age;
}
public double getScore() { return score; }
public void setScore(double score) {
if (score < 0 || score > 100) throw new IllegalArgumentException("成绩无效: " + score);
this.score = score;
}
public String getGrade() {
if (score >= 90) return "优秀";
if (score >= 80) return "良好";
if (score >= 60) return "及格";
return "不及格";
}
public String toString() {
return String.format("%s (年龄:%d, 成绩:%.1f, 等级:%s)", name, age, score, getGrade());
}
}
public static void main(String[] args) {
Student s1 = new Student("张三", 20, 92.5);
Student s2 = new Student("李四", 19, 78.0);
System.out.println(s1);
System.out.println(s2);
// 通过setter修改(带验证)
s2.setScore(85.0);
System.out.println("修改后: " + s2);
// 验证失败演示
try {
new Student("", 20, 80);
} catch (IllegalArgumentException e) {
System.out.println("验证失败: " + e.getMessage());
}
}
}
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
# 7.3.6 训练题
改错题:以下代码违反了封装原则,请找出问题并修正:
public class User { public String password; public double salary; } // 外部直接访问 User u = new User(); u.password = ""; u.salary = -100;1
2
3
4
5
6
7
8实践题:设计一个
BankCard类,要求:密码(password)只能通过changePassword(旧密码, 新密码)方法修改;余额(balance)只能通过deposit()和withdraw()方法修改,且余额不能为负数。思考题:封装不只是"把属性设为 private 加 getter/setter"。有人说"给每个字段都加 getter/setter 和公开字段没区别",你同意吗?什么情况下不应该提供 setter?
# 7.4 对象初始化
# 7.4.1 构造方法
构造方法在创建对象时自动调用,用于初始化对象。构造方法的名字必须与类名相同,没有返回类型。
public class Account {
private String name;
private double balance;
// 无参构造
public Account() {
this.name = "未命名";
this.balance = 0;
}
// 有参构造
public Account(String name, double balance) {
this.name = name;
this.balance = balance;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
注意:如果你不写任何构造方法,编译器会自动提供一个默认的无参构造方法。但如果你写了有参构造,编译器就不会自动生成无参构造了。
对比 C++:C++ 有构造函数和析构函数。Java 有构造方法但没有析构函数(有 finalize 方法,但已被弃用),内存由 GC 管理。
# 7.4.2 构造方法重载
public class Account {
private String name;
private double balance;
private String type;
public Account() {
this("未命名", 0, "普通"); // 调用另一个构造方法
}
public Account(String name) {
this(name, 0, "普通");
}
public Account(String name, double balance, String type) {
this.name = name;
this.balance = balance;
this.type = type;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 7.4.3 this关键字
this 指向当前对象的引用:
public class Account {
private String name;
public Account(String name) {
this.name = name; // this.name 是成员变量,name 是参数
}
public Account getThis() {
return this; // 返回当前对象
}
}
2
3
4
5
6
7
8
9
10
11
对比 C++:C++ 的 this 是指针(this->),Java 的 this 是引用(this.)。
# 7.4.4 构造代码块和静态代码块
public class Account {
static int count;
// 静态代码块:类加载时执行一次
static {
count = 0;
System.out.println("静态代码块执行");
}
// 构造代码块:每次创建对象时执行(在构造方法之前)
{
count++;
System.out.println("构造代码块执行");
}
public Account() {
System.out.println("构造方法执行");
}
}
// 执行顺序:静态代码块 → 构造代码块 → 构造方法
**代码块执行顺序的底层原理**:编译器在生成字节码时,会将所有**静态代码块**合并到 `<clinit>` 方法中(类初始化方法),在类首次加载时由 JVM 保证**线程安全**地执行一次。所有**构造代码块**会被编译器插入到每个构造方法的 `<init>` 方法中、`super()` 调用之后、构造方法体代码之前。这就解释了为什么构造代码块在每次创建对象时都执行,而且总是在构造方法体之前执行。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 7.4.5 对象生命周期
public void method() {
Account acc = new Account("张三", 1000); // 对象创建,在堆上分配内存
acc.deposit(500);
} // 方法结束,acc 引用失效,对象变成垃圾,等待 GC 回收
2
3
4
对比 C++:C++ 需要手动 delete 释放堆上对象,栈上对象在作用域结束时自动销毁(RAII)。Java 全部由 GC 自动管理。
# 7.4.6 综合案例:对象生命周期跟踪器
编写一个程序,通过构造方法、初始化块和 toString 演示对象从创建到使用的完整生命周期。
public class LifecycleTracker {
static class TrackedObject {
private static int totalCreated = 0;
private int id;
private String name;
// 静态初始化块(类加载时执行一次)
static {
System.out.println("[类加载] TrackedObject 类被加载");
}
// 实例初始化块(每次创建对象时执行)
{
totalCreated++;
this.id = totalCreated;
System.out.println("[初始化块] 对象#" + id + " 初始化块执行");
}
// 无参构造
TrackedObject() {
this("未命名");
System.out.println("[构造方法] 无参构造完成");
}
// 有参构造
TrackedObject(String name) {
this.name = name;
System.out.println("[构造方法] 对象#" + id + " \"" + name + "\" 创建完成");
}
public static int getTotalCreated() { return totalCreated; }
public String toString() { return "TrackedObject#" + id + "(" + name + ")"; }
}
public static void main(String[] args) {
System.out.println("===== 创建对象 =====");
TrackedObject obj1 = new TrackedObject("Alpha");
System.out.println();
TrackedObject obj2 = new TrackedObject("Beta");
System.out.println();
TrackedObject obj3 = new TrackedObject(); // 无参→调用有参(this)
System.out.println("\n===== 对象信息 =====");
System.out.println(obj1);
System.out.println(obj2);
System.out.println(obj3);
System.out.println("总共创建: " + TrackedObject.getTotalCreated() + " 个对象");
}
}
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
# 7.4.7 训练题
代码题:以下代码的输出顺序是什么?
public class Demo { static { System.out.println("A"); } { System.out.println("B"); } public Demo() { System.out.println("C"); } public static void main(String[] args) { new Demo(); new Demo(); } }1
2
3
4
5
6
7
8
9实践题:创建一个
Order类,提供三个构造方法(无参、只传订单号、传订单号+金额+描述),使用this()实现构造方法的链式调用,避免重复代码。思考题:Java 没有 C++ 的析构函数(
~ClassName()),那么如果一个对象持有了文件句柄或数据库连接等资源,应该如何确保这些资源被正确释放?
# 7.5 static关键字
# 7.5.1 静态变量
public class Account {
static int totalCount = 0; // 所有对象共享
String name; // 每个对象各自拥有
public Account(String name) {
this.name = name;
totalCount++;
}
}
new Account("张三");
new Account("李四");
System.out.println(Account.totalCount); // 2
2
3
4
5
6
7
8
9
10
11
12
13
# 7.5.2 静态方法
public class MathUtils {
public static int max(int a, int b) {
return a > b ? a : b;
}
public static int min(int a, int b) {
return a < b ? a : b;
}
}
// 通过类名调用
int result = MathUtils.max(10, 20);
2
3
4
5
6
7
8
9
10
11
12
# 7.5.3 静态常量
public class Constants {
public static final double PI = 3.14159265;
public static final int MAX_SIZE = 100;
public static final String APP_NAME = "银行系统";
}
System.out.println(Constants.PI);
2
3
4
5
6
7
# 7.5.4 综合案例:计数器与工具类
编写一个程序,展示 static 关键字在计数器(共享状态)和工具类(无需实例化)中的典型应用。
public class StaticDemo {
// 计数器类:static变量在所有实例间共享
static class Counter {
private static int count = 0;
private int id;
Counter() { this.id = ++count; }
public int getId() { return id; }
public static int getCount() { return count; }
public static void reset() { count = 0; }
}
// 工具类:全部是static方法,构造方法私有
static class MathUtil {
private MathUtil() {} // 禁止实例化
public static int max(int... nums) {
int m = nums[0];
for (int n : nums) if (n > m) m = n;
return m;
}
public static boolean isPrime(int n) {
if (n <= 1) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
public static int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
}
public static void main(String[] args) {
// 计数器演示
System.out.println("===== Static计数器 =====");
Counter c1 = new Counter();
Counter c2 = new Counter();
Counter c3 = new Counter();
System.out.println("c1.id=" + c1.getId() + " c2.id=" + c2.getId() + " c3.id=" + c3.getId());
System.out.println("总计: " + Counter.getCount());
// 工具类演示
System.out.println("\n===== Static工具类 =====");
System.out.println("max(3,7,2,9,1) = " + MathUtil.max(3, 7, 2, 9, 1));
System.out.println("isPrime(17) = " + MathUtil.isPrime(17));
System.out.println("gcd(12, 18) = " + MathUtil.gcd(12, 18));
}
}
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
# 7.5.5 训练题
判断题:静态方法中可以直接访问实例变量。( )
代码题:以下代码能编译通过吗?如果不能,说明原因并修正:
public class Counter { private int count = 0; public static void increment() { count++; } }1
2
3
4
5
6实践题:利用
static实现一个单例模式的Config类:只允许创建一个实例,通过Config.getInstance()获取。思考题:什么时候应该用
static方法,什么时候用实例方法?工具类(如Math、Collections)为什么全是static方法?
# 7.6 内部类
# 7.6.1 成员内部类
public class Bank {
private String name = "中国银行";
// 成员内部类
public class Account {
public void showBankName() {
System.out.println(name); // 可以访问外部类的私有成员
}
}
}
Bank bank = new Bank();
Bank.Account acc = bank.new Account();
acc.showBankName();
2
3
4
5
6
7
8
9
10
11
12
13
14
# 7.6.2 静态内部类
public class Bank {
// 静态内部类
public static class Config {
public static String getVersion() {
return "1.0";
}
}
}
System.out.println(Bank.Config.getVersion());
2
3
4
5
6
7
8
9
10
# 7.6.3 匿名内部类
// 接口
interface Callback {
void onComplete(String result);
}
// 使用匿名内部类
Callback callback = new Callback() {
@Override
public void onComplete(String result) {
System.out.println("完成:" + result);
}
};
callback.onComplete("转账成功");
// JDK 8+ 可以用 Lambda 简写(函数式接口)
Callback callback2 = result -> System.out.println("完成:" + result);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 7.6.4 综合案例:事件回调系统
编写一个程序,使用成员内部类、静态内部类和匿名内部类实现一个简单的事件监听系统。
public class EventSystemDemo {
// 接口(事件监听器)
interface EventListener {
void onEvent(String event);
}
// 外部类:事件源
static class EventSource {
private String name;
private EventListener listener;
EventSource(String name) { this.name = name; }
public void setListener(EventListener listener) { this.listener = listener; }
public void trigger(String event) {
System.out.println("[" + name + "] 触发事件: " + event);
if (listener != null) listener.onEvent(event);
}
}
// 静态内部类:日志监听器
static class LogListener implements EventListener {
public void onEvent(String event) {
System.out.println(" [日志] 记录事件: " + event);
}
}
public static void main(String[] args) {
EventSource button = new EventSource("按钮");
EventSource timer = new EventSource("定时器");
// 使用静态内部类
System.out.println("===== 静态内部类 =====");
button.setListener(new LogListener());
button.trigger("点击");
// 使用匿名内部类
System.out.println("\n===== 匿名内部类 =====");
timer.setListener(new EventListener() {
@Override
public void onEvent(String event) {
System.out.println(" [匿名] 处理事件: " + event);
}
});
timer.trigger("超时");
// 使用Lambda(JDK8+,函数式接口)
System.out.println("\n===== Lambda =====");
button.setListener(event -> System.out.println(" [Lambda] 响应事件: " + event));
button.trigger("双击");
}
}
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
# 7.6.5 训练题
选择题:以下关于内部类的说法,正确的是?
- A. 成员内部类可以用
new Outer.Inner()直接创建 B. 静态内部类可以访问外部类的实例变量 C. 匿名内部类可以用 Lambda 替代(限函数式接口) D. 局部内部类可以用 public 修饰
- A. 成员内部类可以用
实践题:定义一个
EventBus类,包含一个接口EventListener(有onEvent(String msg)方法)。使用匿名内部类和 Lambda 两种方式分别创建监听器并调用。思考题:Android 开发中,成员内部类持有外部类的引用可能导致内存泄漏。请解释这个问题的原因,以及如何用静态内部类 + 弱引用来解决。
# 7.7 枚举类
# 7.7.1 枚举定义
public enum AccountType {
SAVINGS, // 储蓄账户
CHECKING, // 活期账户
CREDIT // 信用账户
}
// 使用
AccountType type = AccountType.SAVINGS;
System.out.println(type); // SAVINGS
2
3
4
5
6
7
8
9
# 7.7.2 枚举方法
public enum AccountType {
SAVINGS("储蓄账户", 0.03),
CHECKING("活期账户", 0.01),
CREDIT("信用账户", 0.18);
private String desc;
private double rate;
AccountType(String desc, double rate) {
this.desc = desc;
this.rate = rate;
}
public String getDesc() { return desc; }
public double getRate() { return rate; }
}
// 使用
AccountType type = AccountType.SAVINGS;
System.out.println(type.getDesc()); // 储蓄账户
System.out.println(type.getRate()); // 0.03
// 遍历所有枚举值
for (AccountType t : AccountType.values()) {
System.out.println(t.name() + ": " + t.getDesc());
}
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
对比 C++:C++ 的 enum class 只是整数常量的集合,Java 的枚举是完整的类,可以有属性、方法、构造方法,功能强大得多。
# 7.7.3 综合案例:交通信号灯枚举
编写一个交通信号灯枚举,展示枚举的属性、方法、switch 使用和遍历。
public class TrafficLightDemo {
enum TrafficLight {
RED("红灯", 30, "停"),
YELLOW("黄灯", 5, "注意"),
GREEN("绿灯", 25, "行");
private final String name;
private final int duration;
private final String action;
TrafficLight(String name, int duration, String action) {
this.name = name;
this.duration = duration;
this.action = action;
}
public String getName() { return name; }
public int getDuration() { return duration; }
public TrafficLight next() {
return switch (this) {
case RED -> GREEN;
case GREEN -> YELLOW;
case YELLOW -> RED;
};
}
}
public static void main(String[] args) {
// 遍历所有枚举值
System.out.println("===== 所有信号 =====");
for (TrafficLight light : TrafficLight.values()) {
System.out.printf(" %s: %s %d秒 → %s%n",
light.name(), light.getName(), light.getDuration(), light.action);
}
// switch匹配
System.out.println("\n===== 信号切换模拟 =====");
TrafficLight current = TrafficLight.RED;
for (int i = 0; i < 6; i++) {
System.out.println(" 当前: " + current.getName() + " → 下一个: " + current.next().getName());
current = current.next();
}
// 枚举比较
System.out.println("\n===== 枚举比较 =====");
System.out.println("RED == RED: " + (TrafficLight.RED == TrafficLight.RED));
System.out.println("RED.ordinal(): " + TrafficLight.RED.ordinal());
System.out.println("valueOf(\"GREEN\"): " + TrafficLight.valueOf("GREEN"));
}
}
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
# 7.7.4 训练题
实践题:定义一个
Season枚举,包含四季(SPRING、SUMMER、AUTUMN、WINTER),每个季节有中文名和平均温度两个属性。编写代码遍历所有季节并输出信息。代码题:枚举可以用在
switch语句中,编写一个方法接收AccountType枚举参数,根据不同类型返回不同的利率。思考题:为什么《Effective Java》推荐使用枚举来实现单例模式?相比普通的单例写法,枚举单例有什么优势?