从 TypeScript 到 Java(4):访问修饰符与作用域 —— Java 的封装哲学
从 TypeScript 到 Java(4):访问修饰符与作用域 —— Java 的封装哲学
系列导读:
在前几篇中,我们认识了 Java 的类结构、入口与类型系统。
这一篇,我们要谈谈面向对象的核心思想之一:封装(Encapsulation)。Java 的封装机制靠 访问修饰符(Access Modifiers) 来实现。
对 TypeScript 开发者而言,概念并不陌生,但 Java 的实现更严格、更体系化。
TypeScript 的访问修饰符更像“开发者约定”,而 Java 的访问修饰符是真正的“语言边界”。
在 Java 中,你不能随意访问一个类的内部成员——编译器会直接阻止你。
而在 TypeScript 中,private 和 protected 只是语法层面的约束(编译后仍能访问)。
🧩 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 的限制是编译级、字节码级的安全保障。
🔒 封装的意义
封装不仅仅是“隐藏变量”,更是:
- 防止外部破坏对象状态;
- 确保数据通过受控方式修改(通过 getter/setter);
- 保持类的内部自由,不影响外部依赖;
- 为将来扩展预留空间。
示例:
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
-
实战练习:
- 创建两个包:
model和service - 在
model中写一个带 private 字段的类 - 在
service包中尝试访问,观察编译器行为
- 创建两个包:
-
思考:
TypeScript 是否应该引入运行时访问限制?为什么 Java 坚持编译时安全?
👉下一篇预告
第 5 篇:《构造函数与对象创建:理解 Java 的实例化模型》
我们将从 new 关键字出发,深入理解 Java 对象的创建生命周期——从内存分配到构造器调用。
