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(私有的)。
  • 提供公共的方法(通常是 publicgettersetter)来访问和修改这些属性。这些方法可以包含验证逻辑、计算逻辑等。

2. 为什么要封装?

  • 控制访问,增强安全性:防止外部代码随意修改对象的内部状态,可能导致数据不一致或无效状态。
  • 隐藏实现细节:外部代码只需要知道如何使用类提供的公共方法,无需关心其内部如何实现。这使得代码更易于维护和修改。
  • 实现数据验证:在 setter 方法中可以检查传入的值是否有效。
  • 提高代码可读性和可维护性:代码逻辑更清晰。

3. 如何实现封装?

主要通过使用访问修饰符和提供 gettersetter 方法。

  • 声明属性为 private
    public class Person {
        private String name; // 私有属性
        private int age;     // 私有属性
    }
    

  • 提供 publicgetter 方法(用于读取属性值):
    public String getName() {
        return name;
    }
    
    public int getAge() {
        return age;
    }
    

  • 提供 publicsetter 方法(用于修改属性值,通常包含验证逻辑):
    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 并提供 publicgettersetter(或其他业务方法)来控制对对象内部状态的访问和修改。这增强了数据安全性、隐藏了实现细节,并使代码更易于维护。

通过合理使用包和封装,可以编写出结构清晰、易于维护、安全可靠的 Java 程序。

更多推荐