一、 什么是封装?
封装(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):
- 封装让类对扩展开放,对修改关闭。
- 例如可以添加新的方法或逻辑,而不会影响外部使用的接口。
评论留言
欢迎您,!您可以在这里畅言您的的观点与见解!