Java 中包和封装的详细教程
·
Java 包与封装详解
第一部分:包 (Package)
1. 什么是包?
包是 Java 中用于组织类和接口的一种机制。可以将包理解为一个文件夹或命名空间,它有以下主要作用:
- 避免命名冲突:不同包中可以有相同名字的类。
- 提供访问保护:包级别的访问控制(如
default访问权限)。 - 更好地组织代码:将功能相关的类放在同一个包中,便于查找和管理。
- 便于代码分发和重用:可以方便地将整个包作为一个库发布。
2. 创建包
- 物理结构:在文件系统中,包对应于目录结构。例如,包
com.example.utils对应目录com/example/utils(在类路径下)。 - 声明包:在 Java 源文件(
.java)的第一行(注释除外)使用package关键字声明该类所属的包。package com.example.utils; // 声明该类属于 com.example.utils 包 public class StringHelper { // ... 类的内容 ... } - 编译与运行:
- 编译时,编译器会根据
package语句将.class文件输出到对应的目录下。 - 运行时,JVM 需要知道类路径(
classpath),以便在这些目录结构中查找.class文件。
- 编译时,编译器会根据
3. 使用包中的类
- 完全限定名:可以直接使用类的完全限定名(包名 + 类名)。
com.example.utils.StringHelper helper = new com.example.utils.StringHelper(); - import 语句:为了简化代码,可以使用
import语句导入单个类或整个包。- 导入单个类:
import com.example.utils.StringHelper; public class Main { public static void main(String[] args) { StringHelper helper = new StringHelper(); // 无需写包名 // ... 使用 helper ... } } - 导入整个包:
import com.example.utils.*; // 导入 com.example.utils 包下的所有类 public class Main { public static void main(String[] args) { StringHelper helper1 = new StringHelper(); AnotherHelper helper2 = new AnotherHelper(); // 假设 AnotherHelper 也在该包下 // ... 使用 helper1, helper2 ... } }
注意:
import只导入包下的类,不包括其子包。import java.util.*;不会导入java.util.concurrent中的类。 - 导入单个类:
- 静态导入 (
import static):用于导入类的静态成员(静态变量和静态方法),可以直接使用成员名而无需类名限定。import static java.lang.Math.PI; // 导入 Math 类的 PI 常量 import static java.lang.Math.sqrt; // 导入 Math 类的 sqrt 方法 public class Circle { public double area(double radius) { return PI * radius * radius; // 直接使用 PI } public double diagonal(double side) { return sqrt(2) * side; // 直接使用 sqrt } }
4. 包访问权限 (default)
如果一个类、方法或变量没有显式指定访问修饰符(public, protected, private),那么它具有包访问权限(也称为 default 或包私有)。
- 具有包访问权限的成员只能被同一个包中的其他类访问。
- 不同包中的类无法访问这些成员。
package com.example.utils; class InternalHelper { // 没有 public 修饰符,包访问权限 void doSomething() { // 没有修饰符,包访问权限 System.out.println("Doing internal work..."); } } public class PublicHelper { public void useInternal() { InternalHelper internal = new InternalHelper(); // 同一个包,可以访问 internal.doSomething(); // 同一个包,可以访问 } }package com.example.app; import com.example.utils.PublicHelper; // import com.example.utils.InternalHelper; // 错误!InternalHelper 是包访问权限,不能从其他包导入 public class Main { public static void main(String[] args) { PublicHelper publicHelper = new PublicHelper(); publicHelper.useInternal(); // 可以调用 PublicHelper 的 public 方法 // InternalHelper internal = new InternalHelper(); // 错误!无法访问 InternalHelper // internal.doSomething(); // 错误! } }
第二部分:封装 (Encapsulation)
1. 什么是封装?
封装是面向对象编程(OOP)的四大基本特性之一(其他三个是继承、多态、抽象)。封装的核心思想是:
- 将数据(属性)和操作数据的方法(行为)捆绑在一起,形成一个独立的单元(即类)。
- 限制对对象内部数据和实现细节的直接访问。通常将属性声明为
private(私有的)。 - 提供公共的方法(通常是
public的getter和setter)来访问和修改这些属性。这些方法可以包含验证逻辑、计算逻辑等。
2. 为什么要封装?
- 控制访问,增强安全性:防止外部代码随意修改对象的内部状态,可能导致数据不一致或无效状态。
- 隐藏实现细节:外部代码只需要知道如何使用类提供的公共方法,无需关心其内部如何实现。这使得代码更易于维护和修改。
- 实现数据验证:在
setter方法中可以检查传入的值是否有效。 - 提高代码可读性和可维护性:代码逻辑更清晰。
3. 如何实现封装?
主要通过使用访问修饰符和提供 getter 和 setter 方法。
- 声明属性为
private:public class Person { private String name; // 私有属性 private int age; // 私有属性 } - 提供
public的getter方法(用于读取属性值):public String getName() { return name; } public int getAge() { return age; } - 提供
public的setter方法(用于修改属性值,通常包含验证逻辑):public void setName(String name) { this.name = name; // 这里可以添加验证,比如检查 name 是否为空 } public void setAge(int age) { if (age >= 0) { // 验证年龄不能为负数 this.age = age; } else { System.out.println("年龄不能为负数!"); // 或者抛出异常 // throw new IllegalArgumentException("年龄不能为负数!"); } }
4. 示例:一个完整的封装类
public class BankAccount {
// 私有属性
private String accountNumber;
private double balance;
// 构造方法 (Constructor)
public BankAccount(String accountNumber, double initialBalance) {
this.accountNumber = accountNumber;
if (initialBalance >= 0) {
this.balance = initialBalance;
} else {
this.balance = 0.0;
System.out.println("初始余额不能为负,已设置为 0.0");
}
}
// Getter 方法
public String getAccountNumber() {
return accountNumber;
}
public double getBalance() {
return balance;
}
// Setter 方法 - 通常不直接提供 setBalance,而是通过存款/取款操作修改
// 存款方法 (Deposit)
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
System.out.println("成功存入: $" + amount);
} else {
System.out.println("存款金额必须大于0!");
}
}
// 取款方法 (Withdraw)
public void withdraw(double amount) {
if (amount > 0) {
if (amount <= balance) {
balance -= amount;
System.out.println("成功取出: $" + amount);
} else {
System.out.println("余额不足!");
}
} else {
System.out.println("取款金额必须大于0!");
}
}
// 其他业务方法...
}
5. 使用封装的类
public class Main {
public static void main(String[] args) {
BankAccount myAccount = new BankAccount("123456789", 1000.0);
// 无法直接访问 private 属性
// myAccount.balance = -500; // 编译错误!
// 通过公共方法操作
System.out.println("当前余额: $" + myAccount.getBalance()); // 输出: 当前余额: $1000.0
myAccount.deposit(500.0); // 输出: 成功存入: $500.0
System.out.println("当前余额: $" + myAccount.getBalance()); // 输出: 当前余额: $1500.0
myAccount.withdraw(2000.0); // 输出: 余额不足!
myAccount.withdraw(-100.0); // 输出: 取款金额必须大于0!
myAccount.withdraw(800.0); // 输出: 成功取出: $800.0
System.out.println("当前余额: $" + myAccount.getBalance()); // 输出: 当前余额: $700.0
}
}
总结
- 包是组织 Java 类、防止命名冲突、控制访问权限的重要机制。通过
package声明包,通过import使用其他包中的类。 - 封装是 OOP 的核心原则,通过将属性设为
private并提供public的getter和setter(或其他业务方法)来控制对对象内部状态的访问和修改。这增强了数据安全性、隐藏了实现细节,并使代码更易于维护。
通过合理使用包和封装,可以编写出结构清晰、易于维护、安全可靠的 Java 程序。
更多推荐



所有评论(0)