从 TypeScript 到 Java(4):访问修饰符与作用域 —— Java 的封装哲学

系列导读:
在前几篇中,我们认识了 Java 的类结构、入口与类型系统。
这一篇,我们要谈谈面向对象的核心思想之一:封装(Encapsulation)

Java 的封装机制靠 访问修饰符(Access Modifiers) 来实现。
对 TypeScript 开发者而言,概念并不陌生,但 Java 的实现更严格、更体系化。

TypeScript 的访问修饰符更像“开发者约定”,而 Java 的访问修饰符是真正的“语言边界”。
在 Java 中,你不能随意访问一个类的内部成员——编译器会直接阻止你。
而在 TypeScript 中,privateprotected 只是语法层面的约束(编译后仍能访问)。

🧩 Java 四种访问修饰符

Java 有四种访问级别,从最开放最封闭

修饰符 访问范围 说明
public 任何地方都能访问 对外公开接口
(默认)(包级访问) 同一个包内可访问 不写修饰符时的默认行为
protected 同包内 + 子类可访问 多用于继承场景
private 仅类内部可访问 完全封装的实现细节

视觉化理解:

+----------------------------+
| public: 任何地方都能访问  |
|----------------------------|
| protected: 同包 + 子类    |
|----------------------------|
| (default): 同包           |
|----------------------------|
| private: 仅类内部         |
+----------------------------+

🧱 示例:完整的访问控制结构

package com.example.demo;

public class Person {
    public String name;         // 公开属性
    protected int age;          // 同包或子类可访问
    String city;                // 包级访问(默认)
    private String password;    // 仅类内访问

    public Person(String name, int age, String city, String password) {
        this.name = name;
        this.age = age;
        this.city = city;
        this.password = password;
    }

    public void introduce() {
        System.out.println("Hi, I'm " + name);
    }

    private void showPassword() {
        System.out.println("Password: " + password);
    }
}

在另一个类中访问这些字段:

public class Main {
    public static void main(String[] args) {
        Person p = new Person("Alice", 25, "Beijing", "1234");

        System.out.println(p.name); // ✅ public 可访问
        System.out.println(p.age);  // ❌ protected,不同包不行
        System.out.println(p.city); // ❌ 默认访问,同包才行
        System.out.println(p.password); // ❌ private,完全不可访问
    }
}

🔍 TypeScript 对比示例

class Person {
  public name: string;
  protected age: number;
  private password: string;

  constructor(name: string, age: number, password: string) {
    this.name = name;
    this.age = age;
    this.password = password;
  }
}

const p = new Person("Alice", 25, "1234");

console.log(p.name);      // ✅
console.log(p.age);       // ❌ TS 报错,但编译后仍可访问
console.log(p["password"]); // ⚠️ 可以绕过

差异关键点:

  • TypeScript 的限制是静态层面的警告
  • Java 的限制是编译级、字节码级的安全保障

🔒 封装的意义

封装不仅仅是“隐藏变量”,更是:

  1. 防止外部破坏对象状态;
  2. 确保数据通过受控方式修改(通过 getter/setter);
  3. 保持类的内部自由,不影响外部依赖;
  4. 为将来扩展预留空间。

示例:

public class Account {
    private double balance;

    public double getBalance() {
        return balance;
    }

    public void deposit(double amount) {
        if (amount > 0) balance += amount;
    }
}

外部无法直接操作 balance,只能通过 deposit() 这样的受控方法。这在大型工程中极其重要。
它能防止无意的“外部篡改”,保证类的不变性与可维护性

🧭 包访问与模块边界

当你不写访问修饰符时,Java 默认使用“包级访问(Package-Private)”。

这意味着:

  • 同一个包(package com.example.demo;)中的类可以互访;
  • 不同包的类就无法直接访问。

这种机制在大型项目中用于构建“模块边界”:

比如 com.company.internal 只供内部模块访问,而 com.company.api 才是对外接口。

这比 TypeScript 的模块导出(export)更严格。

🧬 子类继承与 protected

protected 的主要场景就是继承。

class Animal {
    protected String type = "Animal";
}

class Dog extends Animal {
    public void bark() {
        System.out.println(type + " barks!");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog d = new Dog();
        d.bark(); // 输出:Animal barks!
    }
}

protected 允许子类访问父类成员,即使它们不在同一个包内。
但外部类仍然无权访问。

🧩 可视化理解(作用域层级图)

┌─────────────────────────────────────────────┐
│ 外部模块 / 其他包                           
│   ├── 仅能访问 public                     
│   │                                      
│ 同包类 ─────────────────────────────────┐
│   ├── 可访问 public + protected + default 
│   │                                      
│ 子类(跨包) ────────────────────────────┐ 
│   ├── 可访问 public + protected          
│   │                                       
│ 本类内部 ────────────────────────────────┐
│   ├── 可访问所有(包括 private)          
└─────────────────────────────────────────────┘ 
                                                

这张图是理解 Java 封装哲学的关键。
Java 的“边界意识”非常明确,不仅让代码安全,也让职责更清晰。

🧠 小结

关键点 说明
public 完全开放,对外 API
protected 给子类使用,允许继承访问
(default) 包内部共享
private 类内私有,完全封闭
TypeScript 对比 TS 修饰符是语法层限制,Java 是编译级限制
封装意义 控制访问、保护数据、提升模块稳定性

🚀 延伸阅读与实践

  • 官方文档:Java Access Control

  • 实战练习:

    • 创建两个包:modelservice
    • model 中写一个带 private 字段的类
    • service 包中尝试访问,观察编译器行为
  • 思考:
    TypeScript 是否应该引入运行时访问限制?为什么 Java 坚持编译时安全?

👉下一篇预告

第 5 篇:《构造函数与对象创建:理解 Java 的实例化模型》
我们将从 new 关键字出发,深入理解 Java 对象的创建生命周期——从内存分配到构造器调用。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐