Java 包与可见性
package 组织与 public/protected/包私有/private 可见性如何约束 API 边界。
#concept
#growing
#dev
#java
[!info] related notes
Java 包与可见性
这个概念为什么出现
大型代码库靠边界生存:哪些类型对外,哪些是内部细节。Java 用包名组织命名空间,用访问修饰符控制成员与类型可见性。
[!abstract] 一句话理解
package定义命名空间与目录约定;访问修饰符按声明位置、所在包和继承关系控制谁能访问类型与成员,是 API 设计的第一道墙。
最小可运行示例
场景:订单模块对外只暴露服务,仓储实现包内可见
// 保存为 shop/order/OrderService.java
package shop.order;
public class OrderService {
private final OrderRepository repo = new OrderRepository();
public String place(String sku) {
return repo.save(sku);
}
}
// 包私有:包外不能直接引用此类型(即使仍在同一模块)
class OrderRepository {
String save(String sku) { return "saved-" + sku; }
}
// 演示入口可放同包或测试
class OrderServiceDemo {
public static void main(String[] args) {
System.out.println(new OrderService().place("S1"));
}
}
建议运行(需按包目录放置后编译):
# 在源码根目录
javac shop/order/*.java && java shop.order.OrderServiceDemo
期望输出:
saved-S1
结合场景再看三个关注点
- public 类型才是跨包 API 候选。
- 包私有实现类减少外部依赖面。
- private 字段防止外部破坏不变量。
核心概念与准确模型
1. 修饰符(成员)
| 修饰符 | 同类 | 同包 | 子类(异包) | 世界 |
|---|---|---|---|---|
| private | ✓ | |||
| 包私有(默认) | ✓ | ✓ | ||
| protected | ✓ | ✓ | ✓ | |
| public | ✓ | ✓ | ✓ | ✓ |
2. 类型可见性
- 顶层类型:public 或包私有
- javac 的文件编译约定要求 public 顶层类型名与文件名一致,因此一个 .java 文件至多有一个;嵌套类型另有可见性规则。
3. 模块导出
- 模块系统用
exports再收紧(见模块篇)
边界情况与反直觉行为
- 跨包访问 protected 实例成员必须发生在子类实现中;通过引用访问时,接收者静态类型须为该子类或其子类型,不能任取父类对象。同包访问不受此子类接收者限制。
- private 成员可被同一顶层类型内的嵌套类型访问;子包与父包是不同包,没有额外包私有权限。
- 表格以可访问的声明类型为前提;模块导出和读取关系仍可限制 public 访问。反射也受到模块封装,不能任意绕过。
常见误区
[!warning] 常见误区:所有类全部 public 扩大耦合与破坏面;默认应最小可见。
工程实践
- 先 private,必要时再放宽。
- API 与 impl 分包(
..api/..internal)。 - 避免 protected 字段,优先 protected 方法。
- 与 JPMS 联用进一步隐藏。
本节总结
- 包=命名空间;修饰符=边界
- 最小可见性
- 为模块与分层铺路
自测题
- 普通类中无访问修饰符的字段或方法谁可见?
- 为什么字段优先 private?
参考答案
- 同包可见(包私有)。
- 隐藏表示,强制经方法维护不变量。
延伸阅读与资料来源
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Controlling Access | 教程 | 访问控制 |
| JLS Access Control | 规范 | 可见性 |