主题
Chapter 08 · 封装与构造器
把"重要的东西"锁在保险柜里,只留一扇门进出 — 这就是封装。
🎯 本章目标
- 理解 为什么需要封装
- 掌握 访问修饰符(private / default / protected / public)
- 学会用 getter / setter 控制属性访问
- 深入构造器:重载、互调、初始化顺序
- 理解 static 关键字 三大用法
1. 什么是封装?银行卡的故事
银行卡的密码:
- 你 不能直接 改写卡片上的密码(藏起来)
- 必须通过 ATM 输入"原密码 + 新密码",经过校验 才能改
如果密码可以被任意人随便改,会怎样?灾难。
封装 就是:把字段藏起来(private),只通过受控的方法访问(public 的 get/set)。
java
public class BankAccount {
private String password; // 藏起来
private double balance;
public boolean changePassword(String oldPwd, String newPwd) {
if (this.password.equals(oldPwd)) { // 校验
this.password = newPwd;
return true;
}
return false;
}
}2. 四种访问修饰符
| 修饰符 | 同类 | 同包 | 子类 | 任何 |
|---|---|---|---|---|
private | ✅ | ❌ | ❌ | ❌ |
default(不写) | ✅ | ✅ | ❌ | ❌ |
protected | ✅ | ✅ | ✅ | ❌ |
public | ✅ | ✅ | ✅ | ✅ |
实战指南
| 场景 | 推荐 |
|---|---|
| 字段(属性) | private(强烈推荐) |
| 工具方法(仅本类用) | private |
| 子类需要继承的方法 | protected |
| 对外提供的服务 | public |
| 默认 | 不要省略,明确写出 |
3. Getter 和 Setter:受控的"门"
java
public class Person {
private String name;
private int age;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年龄必须在 0~150");
}
this.age = age;
}
}好处:
- 可以加 校验逻辑(如年龄不能为负)
- 可以加 日志、缓存、懒加载
- 实现细节可以改(比如内部存毫秒,对外暴露 LocalDate)
💡 IDE 自动生成:IntelliJ IDEA 中按
Alt + Insert→ Getter and Setter,一键生成。
4. 构造器深入
4.1 构造器重载
构造器也能像方法一样重载:
java
public class Book {
private String title;
private String author;
private double price;
public Book() { } // 无参
public Book(String title) { this.title = title; }
public Book(String title, String author) {
this.title = title;
this.author = author;
}
public Book(String title, String author, double price) {
this.title = title;
this.author = author;
this.price = price;
}
}4.2 构造器互调(this(...))
避免重复代码:
java
public class Book {
public Book() { this("未知"); }
public Book(String title) { this(title, "未知"); }
public Book(String title, String author) { this(title, author, 0); }
public Book(String title, String author, double price) {
this.title = title;
this.author = author;
this.price = price;
}
}⚠️
this(...)必须是 构造器的第一行!
4.3 默认构造器
不写任何构造器时,编译器会送你一个无参的:
java
class A {
// 编译器自动加:A() {}
}
class B {
B(String s) {} // 自定义了构造器
// ❌ 编译器不再送无参的,所以 new B() 会报错
}5. 字段初始化的 4 种方式与顺序
java
public class Init {
private int a; // ① 默认值(0)
private int b = 10; // ② 显式赋值
private int c;
private int d;
{ // ③ 实例初始化块(少见)
c = 30;
}
public Init() { // ④ 构造器
d = 40;
}
}执行顺序(同一对象内):
① 默认值(0/null/false) → ② 显式赋值 + ③ 初始化块(按代码顺序) → ④ 构造器如果有 static 字段:类加载时初始化一次,所有对象共享。
6. static 关键字三大用法
6.1 静态字段(类变量)
所有对象共享同一份:
java
public class Counter {
static int total = 0; // 静态字段
int id;
public Counter() {
total++; // 每创建一个对象,total 加 1
id = total;
}
}
Counter c1 = new Counter(); // total=1, c1.id=1
Counter c2 = new Counter(); // total=2, c2.id=2
Counter c3 = new Counter(); // total=3, c3.id=3
System.out.println(Counter.total); // 36.2 静态方法
不需要先 new 对象就能调:
java
public class MathUtil {
public static int max(int a, int b) {
return a > b ? a : b;
}
}
int m = MathUtil.max(3, 5); // 直接通过类名调🚫 静态方法里不能用
this!(因为还没对象呢) 🚫 静态方法里不能直接访问非静态字段/方法!
6.3 静态代码块
类加载时执行一次:
java
public class Config {
static String url;
static {
url = loadFromFile();
System.out.println("配置加载完毕");
}
}常用于:加载驱动(JDBC)、读配置、初始化静态变量。
7. 完整封装示例
java
public class BankAccount {
private String accountNo;
private double balance;
private static double INTEREST_RATE = 0.0125;
private static int totalAccounts = 0;
public BankAccount(String accountNo) {
this(accountNo, 0);
}
public BankAccount(String accountNo, double initBalance) {
this.accountNo = accountNo;
this.balance = initBalance;
totalAccounts++;
}
public String getAccountNo() { return accountNo; }
public double getBalance() { return balance; }
public boolean deposit(double amount) {
if (amount <= 0) return false;
balance += amount;
return true;
}
public boolean withdraw(double amount) {
if (amount <= 0 || amount > balance) return false;
balance -= amount;
return true;
}
public static int getTotalAccounts() {
return totalAccounts;
}
}8. 实战练习
| 文件 | 内容 |
|---|---|
Person.java | 标准 Getter/Setter |
BankAccount.java | 完整封装实战 |
InitOrder.java | 字段初始化顺序实验 |
StaticDemo.java | static 三大用法 |
9. 浏览器演示
打开 demo.html:
- 银行账户操作面板
- 访问修饰符可见性测试
- 静态字段共享演示
10. 面试可能会问什么?
Q1: 为什么要封装?
- 隐藏实现 —— 外部不需要知道内部怎么实现
- 校验输入 —— 在 setter 里加校验
- 降低耦合 —— 修改实现不影响外部
- 便于维护 —— 加日志、缓存、懒加载等只改一处
Q2: 不写访问修饰符是 public 吗?
答:不是!是 default(包私有),只有 同一个包 才能访问。
Q3: static 字段和实例字段的区别?
| 对比 | static | 实例 |
|---|---|---|
| 归属 | 类 | 对象 |
| 数量 | 一份(全局共享) | 每个对象一份 |
| 访问 | 类名.字段 | 对象.字段 |
| 内存位置 | 方法区/元空间 | 堆 |
| 生命周期 | 类卸载时销毁 | 对象 GC 时 |
Q4: 为什么 main 要写 static?
答:因为 main 是 JVM 启动的入口,那时还没有任何对象,必须用 static 才能不通过对象直接调用。
Q5: 一个类的 static 块执行多少次?
答:仅一次。在类被加载到 JVM 时执行。
Q6: 构造器能被 static 修饰吗?
答:不能。构造器的本质是初始化对象,必须依赖具体对象,与 static 矛盾。
Q7: 字段初始化顺序?
1. 静态字段默认值 → 静态显式赋值 / 静态块(按顺序)
2. 实例字段默认值 → 实例显式赋值 / 实例块(按顺序)
3. 构造器执行继承场景下还要先父后子(下一章细讲)。
🎁 本章小结
✅ 封装:隐藏字段,暴露受控的 get/set
✅ 4 种访问修饰符:private、default、protected、public
✅ 构造器可重载、可互调(this 必须是第一行)
✅ static 三用:静态字段、静态方法、静态代码块
✅ 类加载时静态先执行,然后才轮到对象🔗 导航
- ⬅️ 上一章:Chapter 07 · 类与对象
- ➡️ 下一章:Chapter 09 · 继承