Skip to content

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);  // 3

6.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.javastatic 三大用法

9. 浏览器演示

打开 demo.html

  • 银行账户操作面板
  • 访问修饰符可见性测试
  • 静态字段共享演示

10. 面试可能会问什么?

Q1: 为什么要封装?

  1. 隐藏实现 —— 外部不需要知道内部怎么实现
  2. 校验输入 —— 在 setter 里加校验
  3. 降低耦合 —— 修改实现不影响外部
  4. 便于维护 —— 加日志、缓存、懒加载等只改一处

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 三用:静态字段、静态方法、静态代码块
✅ 类加载时静态先执行,然后才轮到对象

🔗 导航