跳转至

面向对象编程基础:封装

1. 概述

封装(Encapsulation)是面向对象编程(OOP)的四大基本原则之一(其他三个是继承、多态和抽象)。封装的主要目的是将对象的内部状态(属性)和行为(方法)隐藏起来,只暴露必要的接口供外部访问。通过封装,我们可以控制对对象内部数据的访问,防止外部代码直接修改对象的内部状态,从而提高代码的安全性和可维护性。

2. 封装的概念

2.1 什么是封装?

封装是指将对象的属性和方法包装在一起,并对外部隐藏对象的内部实现细节。通过封装,我们可以将对象的内部状态和行为保护起来,只允许通过特定的方法(通常称为“getter”和“setter”)来访问和修改对象的属性。

2.2 封装的好处

  • 数据隐藏:封装可以隐藏对象的内部实现细节,防止外部代码直接访问或修改对象的内部状态。
  • 提高安全性:通过封装,我们可以控制对对象属性的访问,防止非法或不合理的修改。
  • 增强可维护性:封装使得对象的内部实现可以独立于外部代码进行修改,而不会影响到使用该对象的其他代码。
  • 简化接口:封装可以将复杂的内部实现隐藏起来,只暴露简单的接口供外部使用。

3. 封装的实现方法

在Java中,封装主要通过以下方式实现:

  • 访问修饰符:使用privateprotectedpublic等访问修饰符来控制类成员的访问权限。
  • getter和setter方法:通过提供公共的gettersetter方法来访问和修改私有属性。

3.1 访问修饰符

Java提供了四种访问修饰符来控制类成员的访问权限:

  • private:只能在当前类中访问。
  • protected:可以在当前类、同一个包中的类以及子类中访问。
  • public:可以在任何地方访问。
  • 默认(无修饰符):只能在同一个包中访问。

3.2 getter和setter方法

getter方法用于获取私有属性的值,setter方法用于设置私有属性的值。通过gettersetter方法,我们可以在访问或修改属性时添加额外的逻辑,例如数据验证、日志记录等。

4. 代码示例

4.1 示例1:基本封装

// 定义一个Person类
public class Person {
    // 使用private修饰符将属性封装起来
    private String name;
    private int age;

    // 提供公共的getter和setter方法
    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) { // 在setter方法中添加数据验证
            this.age = age;
        } else {
            System.out.println("年龄不能为负数");
        }
    }
}

// 测试类
public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("Alice");
        person.setAge(25);

        System.out.println("姓名: " + person.getName());
        System.out.println("年龄: " + person.getAge());
    }
}

解释: - Person类中的nameage属性被声明为private,外部代码无法直接访问这些属性。 - 通过getName()getAge()方法可以获取属性的值,通过setName()setAge()方法可以设置属性的值。 - 在setAge()方法中,我们添加了数据验证逻辑,确保年龄不能为负数。

4.2 示例2:封装与数据验证

// 定义一个BankAccount类
public class BankAccount {
    private String accountNumber;
    private double balance;

    public String getAccountNumber() {
        return accountNumber;
    }

    public void setAccountNumber(String accountNumber) {
        this.accountNumber = accountNumber;
    }

    public double getBalance() {
        return balance;
    }

    // 不允许直接设置余额,只能通过存款和取款操作来修改余额
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        } else {
            System.out.println("存款金额必须大于0");
        }
    }

    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
        } else {
            System.out.println("取款金额无效");
        }
    }
}

// 测试类
public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount();
        account.setAccountNumber("123456789");

        account.deposit(1000);
        account.withdraw(500);

        System.out.println("账户余额: " + account.getBalance());
    }
}

解释: - BankAccount类中的balance属性被封装起来,外部代码无法直接修改余额。 - 通过deposit()withdraw()方法来修改余额,并在这些方法中添加了数据验证逻辑。 - 这样可以确保余额不会被非法修改,例如取款金额不能超过当前余额。

4.3 示例3:封装与只读属性

// 定义一个Student类
public class Student {
    private String name;
    private final int studentId; // 使用final修饰符使studentId成为只读属性

    public Student(String name, int studentId) {
        this.name = name;
        this.studentId = studentId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getStudentId() {
        return studentId;
    }

    // 没有setStudentId方法,因为studentId是只读的
}

// 测试类
public class Main {
    public static void main(String[] args) {
        Student student = new Student("Bob", 12345);
        System.out.println("学生姓名: " + student.getName());
        System.out.println("学生ID: " + student.getStudentId());

        // student.setStudentId(67890); // 这行代码会报错,因为studentId是只读的
    }
}

解释: - Student类中的studentId属性被声明为final,这意味着它只能在构造函数中初始化,之后不能被修改。 - 由于没有提供setStudentId()方法,studentId属性是只读的,外部代码无法修改它。

5. 练习题

5.1 简单练习

  1. 创建一个Car类,包含makemodelyear三个私有属性。为这些属性提供gettersetter方法,并在setYear()方法中添加数据验证,确保year不能小于1900。

5.2 中等练习

  1. 创建一个Employee类,包含namesalaryemployeeId三个私有属性。salary属性只能通过raiseSalary()方法来增加,employeeId属性是只读的。编写一个测试类,创建一个Employee对象并测试其方法。

5.3 复杂练习

  1. 创建一个Library类,包含一个books列表(List<Book>),其中Book是一个包含titleauthor属性的类。Library类应该提供addBook()removeBook()findBookByTitle()方法。确保books列表不能被外部直接访问,只能通过Library类提供的方法来操作。

6. 总结

  • 封装是面向对象编程的基本原则之一,通过封装可以将对象的内部状态和行为隐藏起来,只暴露必要的接口供外部访问。
  • 在Java中,封装主要通过访问修饰符(如privatepublic)和getter/setter方法来实现。
  • 封装可以提高代码的安全性可维护性灵活性
  • 通过封装,我们可以在访问或修改属性时添加额外的逻辑,例如数据验证、日志记录等。

掌握封装的概念和实现方法对于编写高质量的面向对象代码至关重要。通过合理使用封装,我们可以构建出更加健壮、安全和易于维护的应用程序。