Skip to content

🔒 封装

封装概述

封装(Encapsulation)是面向对象编程的三大特性之一,它隐藏了对象的内部实现细节,只对外提供必要的接口。

封装的目的

  • 隐藏实现细节:保护对象的内部状态
  • 提高安全性:防止外部直接访问和修改
  • 便于维护:修改内部实现不影响外部使用
  • 提高可读性:清晰的接口,易于理解

封装的实现

使用访问修饰符

java
public class BankAccount {
    // private:私有成员,只能在类内访问
    private String accountNumber;
    private double balance;
    
    // public:公共方法,提供访问接口
    public BankAccount(String accountNumber, double initialBalance) {
        this.accountNumber = accountNumber;
        if (initialBalance >= 0) {
            this.balance = initialBalance;
        } else {
            this.balance = 0;
        }
    }
    
    // Getter 方法:获取账户号码(只读)
    public String getAccountNumber() {
        return accountNumber;
    }
    
    // Getter 方法:获取余额(只读)
    public double getBalance() {
        return balance;
    }
    
    // 存款方法:提供受控的修改接口
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            System.out.println("存款成功,金额: " + amount);
        } else {
            System.out.println("存款金额必须大于 0");
        }
    }
    
    // 取款方法:提供受控的修改接口
    public boolean withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            System.out.println("取款成功,金额: " + amount);
            return true;
        } else {
            System.out.println("取款失败:余额不足或金额无效");
            return false;
        }
    }
}

访问修饰符详解

private(私有)

只能在类内访问:

java
public class Example {
    private int privateVar = 10;
    
    private void privateMethod() {
        System.out.println("私有方法");
    }
    
    public void publicMethod() {
        System.out.println(privateVar);  // ✅ 可以访问
        privateMethod();                 // ✅ 可以调用
    }
}

// 在其他类中
Example e = new Example();
// e.privateVar;      // ❌ 编译错误
// e.privateMethod();  // ❌ 编译错误

default(包访问)

同一个包内可以访问:

java
package com.example;

public class Example {
    int defaultVar = 10;  // 默认访问修饰符
    
    void defaultMethod() {
        System.out.println("默认方法");
    }
}

// 同一个包内的其他类
package com.example;

public class AnotherClass {
    public void method() {
        Example e = new Example();
        System.out.println(e.defaultVar);  // ✅ 可以访问
        e.defaultMethod();                 // ✅ 可以调用
    }
}

protected(受保护)

包内和子类可以访问:

java
package com.example;

public class Parent {
    protected int protectedVar = 10;
    
    protected void protectedMethod() {
        System.out.println("受保护方法");
    }
}

// 子类(可以访问)
package com.example;

public class Child extends Parent {
    public void method() {
        System.out.println(protectedVar);  // ✅ 可以访问
        protectedMethod();                 // ✅ 可以调用
    }
}

public(公共)

所有地方都可以访问:

java
public class Example {
    public int publicVar = 10;
    
    public void publicMethod() {
        System.out.println("公共方法");
    }
}

// 任何地方都可以访问
Example e = new Example();
System.out.println(e.publicVar);  // ✅ 可以访问
e.publicMethod();                 // ✅ 可以调用

Getter 和 Setter 方法

基本 Getter/Setter

java
public class Person {
    private String name;
    private int age;
    
    // Getter 方法
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
    
    // Setter 方法
    public void setName(String name) {
        this.name = name;
    }
    
    public void setAge(int age) {
        this.age = age;
    }
}

带验证的 Setter

java
public class Student {
    private String name;
    private int age;
    private double score;
    
    public void setName(String name) {
        if (name != null && !name.trim().isEmpty()) {
            this.name = name;
        } else {
            throw new IllegalArgumentException("姓名不能为空");
        }
    }
    
    public void setAge(int age) {
        if (age >= 0 && age <= 150) {
            this.age = age;
        } else {
            throw new IllegalArgumentException("年龄必须在 0-150 之间");
        }
    }
    
    public void setScore(double score) {
        if (score >= 0 && score <= 100) {
            this.score = score;
        } else {
            throw new IllegalArgumentException("分数必须在 0-100 之间");
        }
    }
}

只读属性

java
public class Circle {
    private final double radius;  // final 修饰,不可修改
    private final double area;   // 计算属性
    
    public Circle(double radius) {
        this.radius = radius;
        this.area = Math.PI * radius * radius;
    }
    
    // 只提供 Getter,不提供 Setter
    public double getRadius() {
        return radius;
    }
    
    public double getArea() {
        return area;
    }
}

数据验证

输入验证

java
public class Product {
    private String name;
    private double price;
    private int stock;
    
    public void setName(String name) {
        if (name == null || name.trim().isEmpty()) {
            throw new IllegalArgumentException("产品名称不能为空");
        }
        if (name.length() > 100) {
            throw new IllegalArgumentException("产品名称不能超过 100 个字符");
        }
        this.name = name;
    }
    
    public void setPrice(double price) {
        if (price < 0) {
            throw new IllegalArgumentException("价格不能为负数");
        }
        this.price = price;
    }
    
    public void setStock(int stock) {
        if (stock < 0) {
            throw new IllegalArgumentException("库存不能为负数");
        }
        this.stock = stock;
    }
}

业务规则验证

java
public class BankAccount {
    private double balance;
    private static final double MIN_BALANCE = 0;
    private static final double MAX_BALANCE = 1000000;
    
    public void deposit(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("存款金额必须大于 0");
        }
        if (balance + amount > MAX_BALANCE) {
            throw new IllegalArgumentException("账户余额不能超过 " + MAX_BALANCE);
        }
        balance += amount;
    }
    
    public void withdraw(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("取款金额必须大于 0");
        }
        if (balance - amount < MIN_BALANCE) {
            throw new IllegalArgumentException("余额不足");
        }
        balance -= amount;
    }
}

封装的好处

1. 数据保护

java
// 不封装的问题
public class BadExample {
    public int age;
}

// 使用
BadExample e = new BadExample();
e.age = -100;  // ❌ 可以设置无效值

// 封装的好处
public class GoodExample {
    private int age;
    
    public void setAge(int age) {
        if (age >= 0 && age <= 150) {
            this.age = age;
        } else {
            throw new IllegalArgumentException("无效的年龄");
        }
    }
}

2. 实现细节隐藏

java
public class Stack {
    private List<Integer> list = new ArrayList<>();  // 内部使用 ArrayList
    
    public void push(int value) {
        list.add(value);
    }
    
    public int pop() {
        if (list.isEmpty()) {
            throw new RuntimeException("栈为空");
        }
        return list.remove(list.size() - 1);
    }
    
    public boolean isEmpty() {
        return list.isEmpty();
    }
}

// 使用方不需要知道内部使用 ArrayList
// 以后可以改为 LinkedList,不影响使用方

3. 易于维护

java
public class User {
    private String firstName;
    private String lastName;
    
    // 旧版本:返回组合字符串
    public String getName() {
        return firstName + " " + lastName;
    }
    
    // 新版本:可以修改返回格式,不影响使用方
    public String getName() {
        return lastName + ", " + firstName;  // 修改格式
    }
}

完整示例

java
public class EncapsulationExample {
    public static void main(String[] args) {
        // 银行账户示例
        BankAccount account = new BankAccount("123456", 1000);
        
        System.out.println("账户号码: " + account.getAccountNumber());
        System.out.println("初始余额: " + account.getBalance());
        
        account.deposit(500);
        System.out.println("存款后余额: " + account.getBalance());
        
        account.withdraw(200);
        System.out.println("取款后余额: " + account.getBalance());
        
        // 尝试无效操作
        account.withdraw(2000);  // 余额不足
        account.deposit(-100);   // 无效金额
    }
}

class BankAccount {
    private String accountNumber;
    private double balance;
    
    public BankAccount(String accountNumber, double initialBalance) {
        this.accountNumber = accountNumber;
        if (initialBalance >= 0) {
            this.balance = initialBalance;
        } else {
            this.balance = 0;
        }
    }
    
    public String getAccountNumber() {
        return accountNumber;
    }
    
    public double getBalance() {
        return balance;
    }
    
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            System.out.println("存款成功,金额: " + amount);
        } else {
            System.out.println("存款金额必须大于 0");
        }
    }
    
    public boolean withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            System.out.println("取款成功,金额: " + amount);
            return true;
        } else {
            System.out.println("取款失败:余额不足或金额无效");
            return false;
        }
    }
}

最佳实践

1. 使用 private 修饰成员变量

java
// ✅ 好的做法
public class GoodExample {
    private String name;
    private int age;
}

// ❌ 不好的做法
public class BadExample {
    public String name;  // 应该使用 private
    public int age;
}

2. 提供必要的 Getter/Setter

java
// ✅ 好的做法:提供受控访问
public class GoodExample {
    private String name;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        if (name != null && !name.isEmpty()) {
            this.name = name;
        }
    }
}

3. 在 Setter 中添加验证

java
// ✅ 好的做法:添加验证
public void setAge(int age) {
    if (age >= 0 && age <= 150) {
        this.age = age;
    } else {
        throw new IllegalArgumentException("年龄无效");
    }
}

4. 使用 final 保护不可变属性

java
// ✅ 好的做法:使用 final
public class ImmutableExample {
    private final String name;  // 不可变
    private final int age;      // 不可变
    
    public ImmutableExample(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // 只提供 Getter,不提供 Setter
    public String getName() {
        return name;
    }
}

常见错误

1. 忘记封装

java
// ❌ 错误:直接暴露字段
public class BadExample {
    public String name;
    public int age;
}

// ✅ 正确:使用封装
public class GoodExample {
    private String name;
    private int age;
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
}

2. Setter 中没有验证

java
// ❌ 错误:没有验证
public void setAge(int age) {
    this.age = age;  // 可以设置任何值
}

// ✅ 正确:添加验证
public void setAge(int age) {
    if (age >= 0 && age <= 150) {
        this.age = age;
    } else {
        throw new IllegalArgumentException("年龄无效");
    }
}

下一步

掌握了封装后,可以继续学习:


💡 提示:封装是面向对象编程的基础,良好的封装可以保护数据,提高代码的可维护性和安全性