Java 封装(Encapsulation)指南

📖 封装的基本概念

什么是封装?

封装是面向对象编程的四大特性 之一,它将数据(属性)和操作数据的方法(行为)包装在一起,并隐藏内部实现细节,只暴露必要的接口。

封装的核心思想

  • 隐藏实现:对外隐藏内部实现细节
  • 暴露接口:提供公共方法来访问和修改数据
  • 数据保护:防止外部直接访问和修改内部数据

🎯 封装的好处

java

arduino 复制代码
// ❌ 没有封装的问题
public class BadDesign {
    public String name;      // 直接暴露属性
    public int age;         // 外部可以直接修改
    public double salary;   // 没有验证逻辑
}

// ✅ 使用封装的好处
public class GoodDesign {
    private String name;     // 私有属性,隐藏实现
    private int age;        // 只能通过方法访问
    private double salary;  // 可以添加验证逻辑
    
    // 提供公共的访问方法(getter)
    public String getName() {
        return name;
    }
    
    // 提供公共的修改方法(setter)
    public void setName(String name) {
        // 可以添加验证逻辑
        if (name != null && !name.trim().isEmpty()) {
            this.name = name;
        }
    }
}

🔒 访问修饰符(Access Modifiers)

四种访问级别

修饰符 同类 同包 子类 不同包 说明
private 仅本类可见
默认 同包可见
protected 同包或子类可见
public 所有类可见

访问修饰符示例

java

csharp 复制代码
package com.example;

public class AccessDemo {
    // 1. private - 仅本类可见
    private String privateField = "私有字段";
    
    // 2. 默认(包私有)- 同包可见
    String defaultField = "默认字段";
    
    // 3. protected - 同包或子类可见
    protected String protectedField = "受保护字段";
    
    // 4. public - 所有地方可见
    public String publicField = "公共字段";
    
    // 访问私有字段的方法
    public String getPrivateField() {
        return privateField;  // 本类中可以访问private
    }
    
    private void privateMethod() {
        System.out.println("私有方法");
    }
    
    public void testAccess() {
        // 所有访问级别在本类中都可见
        System.out.println(privateField);
        System.out.println(defaultField);
        System.out.println(protectedField);
        System.out.println(publicField);
        privateMethod();
    }
}

// 同包中的另一个类
package com.example;

class SamePackageClass {
    public void test() {
        AccessDemo demo = new AccessDemo();
        
        // System.out.println(demo.privateField);  // ❌ 错误:不可访问
        System.out.println(demo.defaultField);      // ✅ 可以:同包
        System.out.println(demo.protectedField);    // ✅ 可以:同包
        System.out.println(demo.publicField);       // ✅ 可以:公共
    }
}

// 不同包中的类
package other;

import com.example.AccessDemo;

class DifferentPackageClass {
    public void test() {
        AccessDemo demo = new AccessDemo();
        
        // System.out.println(demo.privateField);   // ❌ 错误
        // System.out.println(demo.defaultField);   // ❌ 错误:不同包
        // System.out.println(demo.protectedField); // ❌ 错误:不同包非子类
        System.out.println(demo.publicField);        // ✅ 可以:公共
        
        // 通过公共方法访问私有字段
        System.out.println(demo.getPrivateField());  // ✅ 可以
    }
}

// 不同包中的子类
package other;

import com.example.AccessDemo;

class SubClass extends AccessDemo {
    public void test() {
        // System.out.println(privateField);   // ❌ 错误
        // System.out.println(defaultField);   // ❌ 错误:不同包
        System.out.println(protectedField);   // ✅ 可以:子类
        System.out.println(publicField);      // ✅ 可以:公共
        
        // 通过继承的方法访问
        System.out.println(getPrivateField()); // ✅ 可以
    }
}

🛠️ Getter 和 Setter 方法

标准Getter/Setter模式

java

typescript 复制代码
public class Student {
    // 私有字段
    private String name;
    private int age;
    private String studentId;
    private double gpa;
    
    // 1. Getter方法:获取字段值
    // 命名规范:get + 字段名(首字母大写)
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
    
    public String getStudentId() {
        return studentId;
    }
    
    public double getGpa() {
        return gpa;
    }
    
    // 2. Setter方法:设置字段值
    // 命名规范:set + 字段名(首字母大写)
    public void setName(String name) {
        // 可以添加验证逻辑
        if (name != null && name.length() >= 2) {
            this.name = name;
        } else {
            System.out.println("姓名无效");
        }
    }
    
    public void setAge(int age) {
        // 验证年龄范围
        if (age >= 0 && age <= 150) {
            this.age = age;
        } else {
            System.out.println("年龄必须在0-150之间");
        }
    }
    
    public void setStudentId(String studentId) {
        // 验证学号格式
        if (studentId != null && studentId.matches("\d{10}")) {
            this.studentId = studentId;
        } else {
            System.out.println("学号必须是10位数字");
        }
    }
    
    public void setGpa(double gpa) {
        // 验证GPA范围
        if (gpa >= 0.0 && gpa <= 4.0) {
            this.gpa = gpa;
        } else {
            System.out.println("GPA必须在0.0-4.0之间");
        }
    }
    
    // 3. 只读字段(只有getter,没有setter)
    private final String createdAt = java.time.LocalDate.now().toString();
    
    public String getCreatedAt() {
        return createdAt;
    }
    
    // 4. 只写字段(不推荐,但有特殊用途)
    private String password;
    
    public void setPassword(String password) {
        this.password = password;
    }
    
    // 5. 布尔类型的Getter规范
    private boolean graduated;
    
    // 布尔类型使用is前缀
    public boolean isGraduated() {
        return graduated;
    }
    
    public void setGraduated(boolean graduated) {
        this.graduated = graduated;
    }
    
    // 显示学生信息
    public void displayInfo() {
        System.out.println("学生信息:");
        System.out.println("姓名: " + name);
        System.out.println("年龄: " + age);
        System.out.println("学号: " + studentId);
        System.out.println("GPA: " + gpa);
        System.out.println("创建时间: " + createdAt);
        System.out.println("是否毕业: " + graduated);
    }
    
    public static void main(String[] args) {
        Student student = new Student();
        
        // 使用setter设置值(有验证)
        student.setName("张三");
        student.setAge(20);
        student.setStudentId("2023000001");
        student.setGpa(3.8);
        student.setGraduated(false);
        
        // 使用getter获取值
        System.out.println("学生姓名: " + student.getName());
        System.out.println("学生年龄: " + student.getAge());
        
        // 测试无效输入
        student.setAge(200);  // 会输出错误信息
        student.setGpa(5.0);  // 会输出错误信息
        
        student.displayInfo();
    }
}

高级Getter/Setter技巧

java

java 复制代码
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class AdvancedGetterSetter {
    private String name;
    private List<String> hobbies;  // 可变对象
    private int[] scores;          // 数组
    private final String id;       // final字段
    
    // 构造器初始化final字段
    public AdvancedGetterSetter(String id) {
        this.id = id;
        this.hobbies = new ArrayList<>();
        this.scores = new int[3];
    }
    
    // 1. 返回不可修改的集合(防御性编程)
    public List<String> getHobbies() {
        // 返回副本,防止外部修改内部数据
        return Collections.unmodifiableList(new ArrayList<>(hobbies));
        
        // 或者返回只读视图
        // return Collections.unmodifiableList(hobbies);
    }
    
    // 2. 控制集合的修改
    public void addHobby(String hobby) {
        if (hobby != null && !hobby.trim().isEmpty()) {
            this.hobbies.add(hobby);
        }
    }
    
    public void removeHobby(String hobby) {
        this.hobbies.remove(hobby);
    }
    
    // 3. 数组的Getter/Setter
    public int[] getScores() {
        // 返回数组的副本
        return scores.clone();
    }
    
    public void setScores(int[] scores) {
        // 复制数组,而不是直接引用
        System.arraycopy(scores, 0, this.scores, 0, 
                        Math.min(scores.length, this.scores.length));
    }
    
    // 4. 只读的final字段
    public String getId() {
        return id;
    }
    
    // 5. 计算性属性(没有对应的字段)
    public double getAverageScore() {
        if (scores.length == 0) return 0.0;
        
        int sum = 0;
        for (int score : scores) {
            sum += score;
        }
        return (double) sum / scores.length;
    }
    
    // 6. 链式Setter(返回this)
    public AdvancedGetterSetter setName(String name) {
        this.name = name;
        return this;
    }
    
    // 7. Builder模式结合
    public static class Builder {
        private String id;
        private String name;
        
        public Builder(String id) {
            this.id = id;
        }
        
        public Builder name(String name) {
            this.name = name;
            return this;
        }
        
        public AdvancedGetterSetter build() {
            AdvancedGetterSetter obj = new AdvancedGetterSetter(id);
            obj.name = name;
            return obj;
        }
    }
    
    public static void main(String[] args) {
        AdvancedGetterSetter obj = new AdvancedGetterSetter("001");
        
        // 测试集合保护
        obj.addHobby("篮球");
        obj.addHobby("音乐");
        
        List<String> hobbies = obj.getHobbies();
        System.out.println("爱好: " + hobbies);
        
        // hobbies.add("游泳");  // ❌ 运行时异常:UnsupportedOperationException
        
        // 测试数组保护
        int[] testScores = {90, 85, 95};
        obj.setScores(testScores);
        
        int[] scores = obj.getScores();
        scores[0] = 0;  // 修改的是副本,不影响原数组
        
        System.out.println("平均分: " + obj.getAverageScore());
        
        // 链式调用
        AdvancedGetterSetter obj2 = new AdvancedGetterSetter("002")
            .setName("李四");
    }
}

🏗️ 封装的实际应用

示例1:银行账户类

java

typescript 复制代码
public class BankAccount {
    // 私有字段
    private final String accountNumber;  // 账号不可变
    private String accountHolder;        // 户主姓名
    private double balance;              // 余额
    private String password;             // 密码(不提供getter)
    private boolean isActive;            // 账户状态
    
    // 构造器
    public BankAccount(String accountNumber, String accountHolder, 
                      String password, double initialDeposit) {
        // 参数验证
        validateAccountNumber(accountNumber);
        validateAccountHolder(accountHolder);
        validatePassword(password);
        validateInitialDeposit(initialDeposit);
        
        this.accountNumber = accountNumber;
        this.accountHolder = accountHolder;
        this.password = password;
        this.balance = initialDeposit;
        this.isActive = true;
    }
    
    // 私有验证方法
    private void validateAccountNumber(String accountNumber) {
        if (accountNumber == null || !accountNumber.matches("\d{16}")) {
            throw new IllegalArgumentException("账号必须是16位数字");
        }
    }
    
    private void validateAccountHolder(String accountHolder) {
        if (accountHolder == null || accountHolder.trim().isEmpty()) {
            throw new IllegalArgumentException("户主姓名不能为空");
        }
    }
    
    private void validatePassword(String password) {
        if (password == null || password.length() < 6) {
            throw new IllegalArgumentException("密码至少6位");
        }
    }
    
    private void validateInitialDeposit(double amount) {
        if (amount < 0) {
            throw new IllegalArgumentException("初始存款不能为负");
        }
    }
    
    // Getter方法
    public String getAccountNumber() {
        return accountNumber;
    }
    
    public String getAccountHolder() {
        return accountHolder;
    }
    
    public double getBalance() {
        return balance;
    }
    
    public boolean isActive() {
        return isActive;
    }
    
    // Setter方法(有限制)
    public void setAccountHolder(String accountHolder) {
        validateAccountHolder(accountHolder);
        this.accountHolder = accountHolder;
    }
    
    // 业务方法(封装业务逻辑)
    public void deposit(double amount) {
        if (!isActive) {
            System.out.println("账户已冻结,无法存款");
            return;
        }
        
        if (amount <= 0) {
            System.out.println("存款金额必须大于0");
            return;
        }
        
        balance += amount;
        System.out.printf("存款成功!存入¥%.2f,当前余额¥%.2f%n", amount, balance);
    }
    
    public boolean withdraw(double amount, String password) {
        if (!isActive) {
            System.out.println("账户已冻结,无法取款");
            return false;
        }
        
        if (!authenticate(password)) {
            System.out.println("密码错误");
            return false;
        }
        
        if (amount <= 0) {
            System.out.println("取款金额必须大于0");
            return false;
        }
        
        if (amount > balance) {
            System.out.println("余额不足");
            return false;
        }
        
        balance -= amount;
        System.out.printf("取款成功!取出¥%.2f,当前余额¥%.2f%n", amount, balance);
        return true;
    }
    
    public boolean transfer(BankAccount target, double amount, String password) {
        if (!isActive || !target.isActive) {
            System.out.println("账户状态异常,无法转账");
            return false;
        }
        
        if (!authenticate(password)) {
            System.out.println("密码错误");
            return false;
        }
        
        if (withdraw(amount, password)) {
            target.deposit(amount);
            System.out.printf("向%s转账¥%.2f成功%n", 
                            target.getAccountHolder(), amount);
            return true;
        }
        return false;
    }
    
    // 私有辅助方法
    private boolean authenticate(String password) {
        return this.password.equals(password);
    }
    
    public void changePassword(String oldPassword, String newPassword) {
        if (authenticate(oldPassword)) {
            validatePassword(newPassword);
            this.password = newPassword;
            System.out.println("密码修改成功");
        } else {
            System.out.println("原密码错误");
        }
    }
    
    // 账户管理方法
    public void activate() {
        this.isActive = true;
        System.out.println("账户已激活");
    }
    
    public void deactivate() {
        this.isActive = false;
        System.out.println("账户已冻结");
    }
    
    // 显示账户信息(隐藏敏感信息)
    public void displayAccountInfo() {
        System.out.println("\n=== 账户信息 ===");
        System.out.println("账号: " + accountNumber);
        System.out.println("户主: " + accountHolder);
        System.out.printf("余额: ¥%.2f%n", balance);
        System.out.println("状态: " + (isActive ? "正常" : "冻结"));
        System.out.println("================\n");
    }
    
    public static void main(String[] args) {
        // 创建账户
        BankAccount account1 = new BankAccount(
            "6228480012345678", 
            "张三", 
            "123456", 
            1000.00
        );
        
        BankAccount account2 = new BankAccount(
            "6228480098765432",
            "李四",
            "654321",
            500.00
        );
        
        // 测试功能
        account1.displayAccountInfo();
        
        account1.deposit(500.00);
        account1.withdraw(200.00, "123456");
        account1.withdraw(200.00, "错误密码");  // 密码错误
        
        account1.transfer(account2, 300.00, "123456");
        
        account1.changePassword("123456", "新密码");
        
        account1.deactivate();
        account1.deposit(100.00);  // 账户冻结,无法存款
    }
}

⚠️ 封装的常见错误

错误1:过度暴露内部实现

java

csharp 复制代码
// ❌ 错误的封装
public class BadEncapsulation {
    public List<String> items;  // 直接暴露内部集合
    
    public BadEncapsulation() {
        items = new ArrayList<>();
    }
}

// 使用这个类的代码可以这样做:
BadEncapsulation bad = new BadEncapsulation();
bad.items.add("直接修改");  // 绕过所有验证逻辑
bad.items = null;          // 甚至可以破坏内部状态

// ✅ 正确的封装
public class GoodEncapsulation {
    private List<String> items;
    
    public GoodEncapsulation() {
        items = new ArrayList<>();
    }
    
    public void addItem(String item) {
        if (item != null && !item.trim().isEmpty()) {
            items.add(item);
        }
    }
    
    public List<String> getItems() {
        return Collections.unmodifiableList(new ArrayList<>(items));
    }
}

错误2:Getter/Setter中的逻辑过于复杂

java

typescript 复制代码
// ❌ 不好的做法:在getter/setter中做太多事情
public class ComplexGetterSetter {
    private String data;
    
    public String getData() {
        // 不应该在getter中修改状态
        this.data = processData(this.data);  // ❌ 副作用
        logAccess();                         // ❌ 副作用
        return data;
    }
    
    public void setData(String data) {
        // setter应该只做验证和赋值
        validateData(data);                 // ✅ 可以
        logModification();                  // ⚠️ 小心:可能有副作用
        sendNotification();                 // ❌ 不应该
        updateDatabase();                   // ❌ 不应该
        this.data = data;
    }
}

// ✅ 好的做法:保持简单
public class SimpleGetterSetter {
    private String data;
    
    public String getData() {
        return data;  // 简单返回
    }
    
    public void setData(String data) {
        if (isValid(data)) {
            this.data = data;
        }
    }
    
    // 复杂的业务逻辑放在单独的方法中
    public void updateDataWithNotification(String data) {
        setData(data);
        sendNotification();
        updateDatabase();
    }
}

💡 封装的最佳实践

实践1:使用不可变对象

java

arduino 复制代码
// 不可变类提供更好的封装
public final class ImmutablePerson {
    private final String name;      // final字段
    private final int age;          // final字段
    private final List<String> hobbies;  // 引用不可变
    
    public ImmutablePerson(String name, int age, List<String> hobbies) {
        this.name = name;
        this.age = age;
        // 防御性拷贝
        this.hobbies = Collections.unmodifiableList(new ArrayList<>(hobbies));
    }
    
    // 只有getter,没有setter
    public String getName() { return name; }
    public int getAge() { return age; }
    public List<String> getHobbies() { 
        // 返回不可修改的副本
        return Collections.unmodifiableList(new ArrayList<>(hobbies));
    }
    
    // 修改操作返回新对象
    public ImmutablePerson withName(String newName) {
        return new ImmutablePerson(newName, age, hobbies);
    }
    
    public ImmutablePerson withAge(int newAge) {
        return new ImmutablePerson(name, newAge, hobbies);
    }
}

实践2:使用Builder模式

java

arduino 复制代码
public class Config {
    private final String host;
    private final int port;
    private final int timeout;
    private final boolean sslEnabled;
    
    // 私有构造器
    private Config(Builder builder) {
        this.host = builder.host;
        this.port = builder.port;
        this.timeout = builder.timeout;
        this.sslEnabled = builder.sslEnabled;
    }
    
    // Getter
    public String getHost() { return host; }
    public int getPort() { return port; }
    public int getTimeout() { return timeout; }
    public boolean isSslEnabled() { return sslEnabled; }
    
    // Builder
    public static class Builder {
        // 必填参数
        private final String host;
        private final int port;
        
        // 可选参数(默认值)
        private int timeout = 5000;
        private boolean sslEnabled = false;
        
        public Builder(String host, int port) {
            this.host = host;
            this.port = port;
        }
        
        public Builder timeout(int timeout) {
            this.timeout = timeout;
            return this;
        }
        
        public Builder sslEnabled(boolean sslEnabled) {
            this.sslEnabled = sslEnabled;
            return this;
        }
        
        public Config build() {
            // 验证逻辑
            if (host == null || host.trim().isEmpty()) {
                throw new IllegalArgumentException("Host不能为空");
            }
            if (port <= 0 || port > 65535) {
                throw new IllegalArgumentException("端口无效");
            }
            if (timeout < 0) {
                throw new IllegalArgumentException("超时时间不能为负");
            }
            
            return new Config(this);
        }
    }
    
    public static void main(String[] args) {
        Config config = new Config.Builder("localhost", 8080)
            .timeout(10000)
            .sslEnabled(true)
            .build();
    }
}

实践3:使用接口进一步封装

java

java 复制代码
// 接口定义契约
public interface UserRepository {
    User findById(String id);
    void save(User user);
    void delete(String id);
    List<User> findAll();
}

// 实现类隐藏实现细节
public class DatabaseUserRepository implements UserRepository {
    private DataSource dataSource;
    private final String tableName = "users";
    
    // 隐藏数据库连接细节
    public DatabaseUserRepository(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    
    @Override
    public User findById(String id) {
        // 复杂的数据库查询逻辑
        try (Connection conn = dataSource.getConnection()) {
            // SQL查询...
            return new User();  // 返回结果
        } catch (SQLException e) {
            throw new RepositoryException("查询失败", e);
        }
    }
    
    // 其他实现...
}

// 使用方只关心接口
public class UserService {
    private UserRepository userRepository;
    
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
    
    public User getUser(String id) {
        return userRepository.findById(id);
    }
}

📊 封装总结表

封装级别 实现方式 优点 缺点
字段私有化 使用private修饰字段 防止外部直接修改 需要提供getter/setter
方法公有化 通过public方法访问 可以添加验证逻辑 可能方法过多
返回副本 getter返回对象副本 保护内部数据 性能开销
不可变对象 使用final字段 线程安全,状态固定 修改需要创建新对象
接口封装 通过接口暴露功能 隐藏实现,易于替换 增加复杂度

🎓 封装原则总结

  1. 最小化访问权限:使用最严格的访问修饰符
  2. 暴露最少接口:只提供必要的方法
  3. 防御性编程:验证输入,返回副本
  4. 保持简单:Getter/Setter应该简单直接
  5. 考虑不可变性:尽可能使用final字段
  6. 使用接口:进一步隐藏实现细节
  7. 文档化约束:用文档说明使用限制

记住:封装不是隐藏复杂性,而是管理复杂性。好的封装让类更易于使用、维护和扩展,同时保护内部状态不被意外破坏。

相关推荐
sheji34161 小时前
【开题答辩全过程】以 基于Spring Boot的驾校预约练车系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
p***q781 小时前
springboot整合libreoffice(两种方式,使用本地和远程的libreoffice);docker中同时部署应用和libreoffice
spring boot·后端·docker
zhengzizhe1 小时前
三傻排序(冒泡,选择,插入)Java大白话(老毕登可以跳过)
后端
Zfox_1 小时前
【Go】 协程和 channel
开发语言·后端·golang
a***13141 小时前
【玩转全栈】----Django制作部门管理页面
后端·python·django
Mr.wangh1 小时前
SpringCloud面试题总结
后端·spring·spring cloud
r***86981 小时前
搭建Golang gRPC环境:protoc、protoc-gen-go 和 protoc-gen-go-grpc 工具安装教程
android·前端·后端
AskHarries1 小时前
收到第一封推广邮件:我的 App 正在被看见
前端·后端·产品
w***H6501 小时前
SpringBoot项目如何导入外部jar包:详细指南
spring boot·后端·jar