在什么情况下使用 protected 和 默认(package-private) 才是最佳实践?
| 修饰符 | 可见范围 | 典型使用场景 | 为什么是最佳实践 |
|---|---|---|---|
protected |
本类 + 同包类 + 所有子类(即使在不同包) | 1. 父类中希望子类能覆盖或访问的字段/方法 2. 框架/库设计中,允许扩展点 3. 模板方法模式中的钩子(hook) |
① 保持封装(外部包不能直接访问) ② 给继承体系提供受控扩展点 ③ 避免 public 的“过度暴露” |
| 默认(无修饰符) | 仅同包类 | 1. 包内协作的工具类、常量、辅助方法 2. 同一模块/子系统内部实现细节 3. SPI(Service Provider Interface) 的实现类 |
① 防止外部包误用内部实现 ② 包即“模块边界”,天然的访问控制 ③ 便于重构(包内可随意调整) |
1. protected 最佳实践场景(附代码)
场景 A:模板方法模式(父类定义流程,子类实现细节)
1 | // com.company.framework |
1 | // com.company.app.pdf |
为什么用
protected?
formatBody必须被子类实现 →abstract protectedformatHeader允许子类覆盖 →protected(默认实现仍可被包内类使用)sanitize是内部工具,只给子类用 →protected防止外部直接调用
场景 B:框架提供的扩展点(Spring、Hibernate 等)
1 | // org.springframework.web.servlet |
框架不希望外部随意调用
postHandle,但允许开发者继承扩展 →protected
2. 默认(package-private)最佳实践场景
场景 A:同一包内的工具类 / 常量
1 | // com.company.util |
1 | // com.company.service |
为什么默认?
StringUtils只服务于com.company包内部- 外部包不需要知道这些实现细节
- 包即“模块”,天然边界
场景 B:SPI 实现类(仅由框架加载)
1 | // com.company.plugin.spi |
1 | // META-INF/services/com.company.plugin.spi.Logger |
框架通过
ServiceLoader加载 同包实现,外部不应直接new ConsoleLogger()
3. 决策流程图(何时选哪个?)
graph TD
subgraph 决策流程
A["需要被外部包直接使用?"] -->|是| B["用 public"]
A -->|否| C["是否需要被子类(不同包)访问?"]
C -->|是| D["用 protected"]
C -->|否| E["是否仅同包协作?"]
E -->|是| F["用 默认(无修饰符)"]
E -->|否| G["用 private"]
end
4. 常见误区与避坑
| 误区 | 正确做法 |
|---|---|
把所有字段都设为 protected |
仅子类真正需要的才 protected,其余 private |
用 public 替代 protected “以防万一” |
违反最小权限原则,增加 API 维护负担 |
| 默认修饰符“看不见”就乱用 | 明确包是模块边界,默认 = “包内 API” |
5. 一句话总结
protected:给“继承体系”开后门,只让子类进。- 默认(package-private):给“同包兄弟”留便门,外部别想进。
记忆口诀:
“子类要用 protected,包内协作默认行;
外人想碰?门都没有!”