类的封装
一、 什么是封装?二、 封装的三个步骤三、 封装的完整代码示例类定义:封装的实现使用封装的类:测试代码
四、 `this`关键字的使用代码回顾1. `this` 的作用详解1.1 `this` 是什么?1.2 `this` 的用法
2. 为什么要用 `this`?2.1 没有 `this` 时的问题2.2 使用 `this` 的解决方法
3. 深入理解 `this` 的作用3.1 `this` 的隐含含义运行结果:
3.2 `this` 访问类的属性和方法3.3 `this` 调用构造方法
4. `this` 总结
五、 封装的优势六、 封装的细节与扩展1. 只读和只写属性2. 属性的访问控制级别3. 数据校验逻辑4. 自动生成`getter`和`setter`5. 使用封装设计复杂类
七、 封装与设计原则
一、 什么是封装?
封装(Encapsulation)是面向对象编程(OOP)的核心特性之一。它强调将数据和行为整合到类中,并通过限制外部对数据的直接访问,保护数据的安全性。
简单来说:
封装是对类的内部实现细节进行隐藏,对外部暴露统一的访问接口。外部程序只能通过类提供的公开方法(如getter和setter)与类的内部数据交互,无法直接修改或访问类的内部数据。
封装的核心思想是:隐藏细节,控制访问。
二、 封装的三个步骤
要实现封装,需要遵循以下三个步骤:
将类的属性声明为私有(private)。
私有化后,属性只能在类的内部访问,外部类无法直接修改或读取这些属性。 为私有属性提供getter和setter方法。
getter方法用于获取属性值。setter方法用于设置属性值,并可以在方法中添加逻辑,进行校验和控制。 限制访问级别,根据需要对外提供访问接口。
通过方法来控制数据的读写权限,比如可以设置属性为只读、只写或可读可写。
三、 封装的完整代码示例
以下是一个实现封装的例子:
类定义:封装的实现
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 的用法
访问当前对象的属性:
当方法的参数名或局部变量名与类的属性名相同时,this 用于区分类的属性和方法的局部变量。格式:this.属性名 调用当前对象的方法:
使用this可以调用当前对象的其他方法。格式:this.方法名() 调用当前类的构造方法:
使用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对象,每个对象都有自己的name和salary。this 指代调用方法的具体对象。
代码如下:
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 是当前对象的引用。常见用法:
访问当前对象的属性(this.属性名)。调用当前对象的方法(this.方法名())。在构造方法中调用另一个构造方法(this(参数...))。
在封装中,this 是关键字,它帮助我们正确地处理方法参数和类属性之间的同名问题。
五、 封装的优势
提高代码的安全性
将数据隐藏起来,防止外部代码随意修改对象的内部状态。通过getter和setter方法,可以对数据进行校验或限制,避免出现非法值。 提高代码的可维护性
隐藏实现细节,类的内部实现可以随时修改,而不会影响外部调用代码。如果属性或逻辑需要改变,只需修改类内部的代码,外部使用类的接口无需修改。 增强代码的灵活性
可以根据需要提供只读或只写属性。通过getter和setter方法,可以动态地控制属性值的获取或设置逻辑。 模块化设计
类的属性和行为被封装在一起,形成一个独立的模块,便于代码管理和重用。
六、 封装的细节与扩展
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,完全隐藏;方法根据需要设置为public、protected或默认。
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
在实际开发中,手写getter和setter方法可能较繁琐,IDE(如IntelliJ IDEA或Eclipse)提供了自动生成工具:
在IDE中右键点击代码空白处。选择 Generate 或 Source → Generate Getters and Setters。选择需要生成的方法,点击完成即可。
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
余额不足或取款金额不合法!
七、 封装与设计原则
封装的实现遵循了面向对象设计原则中的以下两点:
单一职责原则(SRP):
每个类封装自己的职责,互不干扰。例如BankAccount类只负责账户相关的操作。 开闭原则(OCP):
封装让类对扩展开放,对修改关闭。例如可以添加新的方法或逻辑,而不会影响外部使用的接口。