1. 先说结论:子类体内到底看得到看不到父类的私有变量
这个问题的标准答案其实只有一句话:子类不能直接访问父类的私有变量,但可以通过父类提供的公有或受保护方法间接访问。
但我发现很多人对这个答案的理解停留在“背结论”的层面,一碰到具体的代码场景就露馅。比如下面这段代码,你有把握一眼说出输出结果吗?
java复制public class Parent {
private String name = "parent";
public String getName() {
return name;
}
}
public class Child extends Parent {
public void print() {
// 这里的 name 能直接用吗?
// System.out.println(name); // 编译报错
System.out.println(getName()); // 可以
}
}
Child 里直接 println(name) 会编译报错,因为 name 是 Parent 的私有字段,Child 的作用域内不可见。但调用 getName() 没问题,因为 getName() 是 public 方法,子类天然继承。
这背后的机制值得说透。Java 的可见性控制发生在编译期,而不是运行期。编译器在解析 name 这个标识符时,会从当前类开始向上逐层查找字段声明,遇到 private 修饰的字段,只要当前类不是声明它的那个类本身,就直接判定为不可见。换句话说,Child 的字节码里根本不会出现对 name 字段的符号引用,编译阶段就被拦住了。
运行期又是另一回事。子类对象在堆内存中确实包含了父类定义的私有字段——它作为对象头之后实例数据区的一部分存在。只是这个字段对子类代码不可见,对 JVM 来说它就在那里。所以有个很经典的追问:子类对象到底“有没有”父类的私有变量?
答案是有,但子类的代码“看不见、摸不着”。字段的存储是物理存在的,可见性是编译期的语法规则。这两件事经常被混为一谈。
还有一个容易踩坑的细节:如果父类和子类各自声明了一个同名变量,会怎样?
java复制public class Parent {
private int value = 10;
public int getParentValue() {
return value;
}
}
public class Child extends Parent {
private int value = 20;
public int getChildValue() {
return value;
}
}
这叫做变量隐藏(Variable Hiding)。子类的 value 和父类的 value 是两个完全独立的字段,各用各的。Child 内部访问 value 得到的是子类自己的 20,但调用继承来的 getParentValue() 时,方法体是在 Parent 类中定义的,它访问 value 时绑定的是父类的那个字段,所以返回 10。很多初学者在这里想当然地认为“子类变量覆盖了父类变量”,实际上字段不存在多态覆盖,只有方法才有多态覆写,字段只会被隐藏。
理解了这个基础,后面几种访问方式才有讨论的意义。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 正攻法:通过父类公开接口间接读写私有变量
最常规、也是实际项目里用得最多的方案,就是让父类提供 getter/setter 或者其他公开方法,子类通过这些方法访问私有变量。这不仅是“能访问”,更是 Java 封装性的体现。
2.1 getter/setter 的正确设计
看一个实际的例子,比如一个订单类:
java复制public class Order {
private String orderNo;
private BigDecimal amount;
private OrderStatus status;
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
public BigDecimal getAmount() {
return amount;
}
public BigDecimal calculatePayableAmount() {
// 在这里做金额校验、优惠计算等逻辑
return amount;
}
}
子类继承 Order 后,虽然不能直接操作 orderNo 字段,但可以调用 getOrderNo() 读取,调用 setOrderNo() 写入。这看起来多了一层调用,实际带来两个关键好处:
第一,字段的读写逻辑统一收口在父类。以后如果要在写入 orderNo 时做格式校验、在读取 amount 时做精度处理,只需要改父类的 getter/setter,所有子类自动生效。如果允许子类直接访问字段,这种统一约束就无从谈起。
第二,子类拿到的不是“裸数据”而是“经过加工的数据”。父类的 getter 里完全可以做额外处理,比如缓存、日志、权限校验。直接访问字段就跳过了这些逻辑,一不小心就埋坑。
2.2 不仅仅是 getter/setter,行为方法更关键
现实中,很多场景不是为了“读取字段”,而是为了让父类执行一段依赖私有状态的逻辑。此时应当调用父类的行为方法,而不是把私有状态暴露出来。
举个例子,一个玩家类:
java复制public class Player {
private int hp;
private int maxHp;
public voi
