Java:类的封装

93°C 05-01-2025 notbyai
最近更新于:2025-01-05 17:58:29

一、 什么是封装?

封装(Encapsulation)是面向对象编程(OOP)的核心特性之一。它强调将数据和行为整合到类中,并通过限制外部对数据的直接访问,保护数据的安全性

简单来说:

  • 封装是对类的内部实现细节进行隐藏,对外部暴露统一的访问接口。
  • 外部程序只能通过类提供的公开方法(如gettersetter)与类的内部数据交互,无法直接修改或访问类的内部数据。

封装的核心思想是:隐藏细节,控制访问


二、 封装的三个步骤

要实现封装,需要遵循以下三个步骤:

  1. 将类的属性声明为私有(private)。
    • 私有化后,属性只能在类的内部访问,外部类无法直接修改或读取这些属性。
  2. 为私有属性提供gettersetter方法。
    • getter方法用于获取属性值。
    • setter方法用于设置属性值,并可以在方法中添加逻辑,进行校验和控制。
  3. 限制访问级别,根据需要对外提供访问接口。
    • 通过方法来控制数据的读写权限,比如可以设置属性为只读、只写或可读可写。

三、 封装的完整代码示例

以下是一个实现封装的例子:

类定义:封装的实现

public class Student {
    // 私有属性
    private String name;
    private int age;

    // Getter方法:获取name属性
    public String getName() {
        return name;
    }

    // Setter方法:设置name属性
    public void setName(String name) {
        this.name = name;
    }

    // Getter方法:获取age属性
    public int getAge() {
        return age;
    }

    // Setter方法:设置age属性(带校验逻辑)
    public void setAge(int age) {
        if (age > 0 && age < 120) {
            this.age = age;
        } else {
            System.out.println("年龄不合法,请输入1-120之间的值!");
        }
    }
}

使用封装的类:测试代码

public class Main {
    public static void main(String[] args) {
        Student student = new Student();

        // 设置属性值
        student.setName("Alice");
        student.setAge(25);

        // 获取属性值
        System.out.println("姓名:" + student.getName()); // 输出:姓名:Alice
        System.out.println("年龄:" + student.getAge()); // 输出:年龄:25

        // 测试非法数据
        student.setAge(-5); // 输出:年龄不合法,请输入1-120之间的值!
    }
}

四、 this关键字的使用

代码回顾

这是一个实现封装的简单类,用于管理Employee的姓名和工资:

public class Employee {
    // 1. 私有属性
    private String name;   // 姓名
    private double salary; // 工资

    // 2. 提供getter方法:获取name属性
    public String getName() {
        return name;
    }

    // 3. 提供setter方法:设置name属性
    public void setName(String name) {
        // this.name 是指当前类的属性,name 是方法参数
        this.name = name;
    }

    // getter方法:获取salary属性
    public double getSalary() {
        return salary;
    }

    // setter方法:设置salary属性
    public void setSalary(double salary) {
        // 数据校验:工资不能为负数
        if (salary > 0) {
            this.salary = salary;
        } else {
            System.out.println("工资必须大于0!");
        }
    }
}

1. this 的作用详解

在封装代码中,this 是一个非常重要的关键字,主要用于解决类的属性和方法参数(或局部变量)同名的问题。

1.1 this 是什么?

  • this 是一个指向当前对象的引用。
  • 具体来说,当类的某个方法被调用时,Java 会将调用该方法的对象的引用(即当前对象)作为隐含参数传递给方法。
  • this 的作用是指代调用方法的当前对象

1.2 this 的用法

  1. 访问当前对象的属性
    • 当方法的参数名或局部变量名与类的属性名相同时,this 用于区分类的属性和方法的局部变量。
    • 格式:this.属性名
  2. 调用当前对象的方法
    • 使用this可以调用当前对象的其他方法。
    • 格式:this.方法名()
  3. 调用当前类的构造方法
    • 使用this()可以在一个构造方法中调用另一个构造方法。

2. 为什么要用 this?

2.1 没有 this 时的问题

setName方法中,参数名是name,而类的属性名也是name。如果不使用this,会出现以下问题:

public void setName(String name) {
    name = name; // 这实际上是在给参数自己赋值,类的属性name没有被修改
}

在上面的代码中,name 是方法的参数,而非类的属性。由于局部变量(参数name)优先级高,它会覆盖同名的类属性。因此,赋值语句 name = name; 只是在把方法参数name赋值给它自己,而不是给类的属性赋值。

2.2 使用 this 的解决方法

为了明确表示赋值的目标是类的属性,而不是方法的参数,我们使用this

public void setName(String name) {
    this.name = name; // this.name 是类的属性,name 是方法的参数
}
  • this.name 表示当前对象的属性name
  • name 表示方法的参数name

3. 深入理解 this 的作用

3.1 this 的隐含含义

假设我们创建了两个Employee对象,每个对象都有自己的namesalarythis 指代调用方法的具体对象。

代码如下:

public class Main {
    public static void main(String[] args) {
        Employee emp1 = new Employee();
        Employee emp2 = new Employee();

        emp1.setName("Alice");
        emp1.setSalary(5000);

        emp2.setName("Bob");
        emp2.setSalary(7000);

        System.out.println(emp1.getName() + " 的工资是 " + emp1.getSalary());
        System.out.println(emp2.getName() + " 的工资是 " + emp2.getSalary());
    }
}
运行结果:
Alice 的工资是 5000.0
Bob 的工资是 7000.0

在这里:

  • emp1.setName("Alice") 中,this 代表 emp1
  • emp2.setName("Bob") 中,this 代表 emp2

每个对象都有独立的属性,this 可以正确地将方法调用与具体的对象绑定。

3.2 this 访问类的属性和方法

this 可以在类中访问属性和方法:

示例:在setter方法中调用getter方法

public void setName(String name) {
    if (name != null && !name.trim().isEmpty()) {
        this.name = name;
    } else {
        System.out.println("姓名无效,设置为默认值!");
        this.name = "默认姓名";
    }

    // 调用getter方法,打印当前设置的值
    System.out.println("当前姓名:" + this.getName());
}

测试代码:

Employee emp = new Employee();
emp.setName("");   // 输出:姓名无效,设置为默认值! 当前姓名:默认姓名

在这里,this.getName() 调用了当前对象的 getName 方法。

3.3 this 调用构造方法

this 还可以用来调用类的其他构造方法,以简化代码。

示例:调用另一个构造方法

public class Employee {
    private String name;
    private double salary;

    // 无参构造方法
    public Employee() {
        this("默认姓名", 0.0); // 调用有参构造方法
    }

    // 有参构造方法
    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public String getName() {
        return name;
    }

    public double getSalary() {
        return salary;
    }
}

测试代码:

public class Main {
    public static void main(String[] args) {
        Employee emp = new Employee(); // 无参构造
        System.out.println(emp.getName()); // 输出:默认姓名
        System.out.println(emp.getSalary()); // 输出:0.0
    }
}

4. this 总结

  • this 是当前对象的引用。
  • 常见用法:
  1. 访问当前对象的属性(this.属性名)。
  2. 调用当前对象的方法(this.方法名())。
  3. 在构造方法中调用另一个构造方法(this(参数...))。

在封装中,this 是关键字,它帮助我们正确地处理方法参数和类属性之间的同名问题。


五、 封装的优势

  1. 提高代码的安全性
    • 将数据隐藏起来,防止外部代码随意修改对象的内部状态。
    • 通过gettersetter方法,可以对数据进行校验或限制,避免出现非法值。
  2. 提高代码的可维护性
    • 隐藏实现细节,类的内部实现可以随时修改,而不会影响外部调用代码。
    • 如果属性或逻辑需要改变,只需修改类内部的代码,外部使用类的接口无需修改。
  3. 增强代码的灵活性
    • 可以根据需要提供只读或只写属性。
    • 通过gettersetter方法,可以动态地控制属性值的获取或设置逻辑。
  4. 模块化设计
    • 类的属性和行为被封装在一起,形成一个独立的模块,便于代码管理和重用。

六、 封装的细节与扩展

1. 只读和只写属性

  • 只读属性: 只提供getter方法,不提供setter方法。
  • 只写属性: 只提供setter方法,不提供getter方法。

示例代码:

public class Account {
    private String accountNumber; // 只读属性
    private double balance;       // 只写属性

    // Getter方法(只读属性)
    public String getAccountNumber() {
        return accountNumber;
    }

    // Setter方法(只写属性)
    public void setBalance(double balance) {
        if (balance >= 0) {
            this.balance = balance;
        } else {
            System.out.println("余额不能为负数!");
        }
    }
}

2. 属性的访问控制级别

Java提供了4种访问控制修饰符,用于限制类、属性和方法的访问权限:

修饰符类内部同包子类跨包
public
protected×
默认(无修饰符)××
private×××
  • 建议:
    • 属性一般使用private,完全隐藏;
    • 方法根据需要设置为publicprotected或默认。

3. 数据校验逻辑

封装的一个重要作用是保护数据的合法性。setter方法通常会包含数据校验逻辑。

例如:

  • 限制年龄范围:
public void setAge(int age) {
    if (age > 0 && age <= 150) {
        this.age = age;
    } else {
        System.out.println("年龄必须在1到150之间!");
    }
}
  • 限制密码长度:
public void setPassword(String password) {
    if (password.length() >= 6 && password.length() <= 20) {
        this.password = password;
    } else {
        System.out.println("密码长度必须在6到20个字符之间!");
    }
}

4. 自动生成getter和setter

在实际开发中,手写gettersetter方法可能较繁琐,IDE(如IntelliJ IDEA或Eclipse)提供了自动生成工具:

  1. 在IDE中右键点击代码空白处。
  2. 选择 GenerateSourceGenerate Getters and Setters
  3. 选择需要生成的方法,点击完成即可。

5. 使用封装设计复杂类

以下是一个复杂的封装类示例:

public class BankAccount {
    private String accountNumber; // 账号
    private double balance;       // 余额
    private String owner;         // 户主

    public BankAccount(String accountNumber, String owner) {
        this.accountNumber = accountNumber;
        this.owner = owner;
        this.balance = 0.0; // 初始化余额为0
    }

    // 获取账号(只读)
    public String getAccountNumber() {
        return accountNumber;
    }

    // 获取余额
    public double getBalance() {
        return balance;
    }

    // 存款方法
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
            System.out.println("存款成功!当前余额:" + balance);
        } else {
            System.out.println("存款金额必须大于0!");
        }
    }

    // 取款方法
    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            System.out.println("取款成功!当前余额:" + balance);
        } else {
            System.out.println("余额不足或取款金额不合法!");
        }
    }
}

测试代码:

public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount("123456", "Alice");

        account.deposit(1000); // 存款
        account.withdraw(500); // 取款
        account.withdraw(700); // 尝试超额取款
    }
}

输出:

存款成功!当前余额:1000.0
取款成功!当前余额:500.0
余额不足或取款金额不合法!

七、 封装与设计原则

封装的实现遵循了面向对象设计原则中的以下两点:

  1. 单一职责原则(SRP):
    • 每个类封装自己的职责,互不干扰。
    • 例如BankAccount类只负责账户相关的操作。
  2. 开闭原则(OCP):
    • 封装让类对扩展开放,对修改关闭。
    • 例如可以添加新的方法或逻辑,而不会影响外部使用的接口。

评论留言

欢迎您,!您可以在这里畅言您的的观点与见解!

0 条评论