经典题解析:public、protected、默认和 private 到底管到哪

图中按访问场景区分权限,跨包子类还需要额外检查接收者类型。

“public 谁都能访问,protected 子类能访问,private 只有自己能访问。”这张口诀表能帮助入门,却很容易在一道跨包继承题上失效。Java 的访问控制不只检查有没有继承关系,还检查访问发生在哪里、对象表达式的类型是什么,以及声明所在的类型本身是否可访问。

先区分类与成员

普通顶层类通常只能声明为 public,或者省略修饰符成为包访问;不能把顶层类随手写成 private 或 protected。成员类则可以使用更多访问级别。讨论方法、字段的可见范围前,要先确认它们所属的类型能被当前代码使用。

即使方法声明为 public,也不能据此推出外部代码总能调用。类的可访问性、模块是否导出相应包等条件,仍可能挡住访问。面试题若不讨论模块,可以先说明以下分析针对通常的类路径环境,避免把简化条件讲成语言唯一情况。

四种权限先看同包

public 成员在声明类型可访问等前提下提供公开入口。没有写修饰符的成员是包访问,通常也叫 package-private;它不是一个叫 default 的关键字。不同包即便名字存在父子式前缀,也不因此互相获得包访问权限。

protected 除了允许同包访问,还给包外子类提供受约束的访问路径。private 则把访问限制在声明所在顶层类的内部范围内,包括该顶层类内部相关的嵌套类型;因此“只有当前这个小类能访问”也不够精确。

判断一个访问是否成立,先画出包边界往往比先画继承树更快。同包中的非子类可以访问 protected 成员,这说明 protected 从来不只是“子类专用”。

protected 不只是“子类可见”

跨包 protected 的接收者限制

设包 p 中有父类 Base,声明 protected 实例字段 value;包 q 中有 Child 继承 Base。在 Child 的实例方法里,用 this.value 访问自己的继承字段可以成立,用一个静态类型为 Child 的参数访问它的 value 也可以成立。

但把参数声明为 Base,再写 base.value,并不会因为当前方法位于 Child 中就自动合法。对于这种包外子类访问受保护实例成员,接收者表达式的类型还须是当前访问子类或其子类。否则,子类就能借继承身份任意窥视不属于自身继承分支的父类对象。

这里说的是实例成员的常见表达式访问,不把规则直接套到 protected 构造器或所有静态成员语法上。那些情况有各自规定。一个讲得清适用范围的例子,比一句“protected 比包访问大一点”更准确。

权限是接口设计的一部分

字段设为 private,不只是为了让编译器少允许几处引用,而是让对象内部状态通过受控方法修改。例如库存扣减需要检查余额非负,公开一个可任意写入的数字,就把不变量交给了所有调用方维护。

同样,protected 可变字段也会把内部表示暴露给子类。以后若想改成延迟计算或增加校验,继承方可能已经依赖旧布局。可以优先提供必要的操作接口,再根据真实扩展需求开放受保护钩子,避免为了方便继承而把所有状态放开。

public 不是越多越好,private 也不是越多越安全。访问修饰符约束源代码层面的合法访问,并不能替代鉴权、数据权限或外部输入检查。公开方法内部仍可能需要判断调用者业务身份。

用编译结果验证直觉

核对这类题时,可以准备两个包,在父类里声明四类字段,分别从同包普通类、异包普通类和异包子类访问,再让编译器指出不成立的行。尤其把 Child 参数换成 Base 参数,能直接观察 protected 的接收者边界。

不要把无法编译的片段写成运行后抛异常:访问控制错误通常就在编译阶段被拒绝。反射等额外访问机制又有其他规则,不应拿来否定普通成员访问题的结论。

如果你在异包子类中拿到一个 Base 类型的对象,为什么“我也是它的子类”仍不足以授权你读取那个对象的 protected 字段?