Inheritance Binding Lab

编译器看左边,虚拟机再看右边

先组合一条合法赋值,再选择成员访问形式。实验台会把“编译期选择”与“运行期分派”拆开显示,避免把字段隐藏、静态隐藏和实例方法重写混成一条规则。

蓝色:编译期 橙色:运行期 绿色:最终结果
合法赋值 ParentChannel ref = new SmsChannel();

固定实验类

字段隐藏 · 静态隐藏 · 方法重写
class ParentChannel {
  String label = "父类字段";

  static String kind() {
    return "父类静态方法";
  }

  String message() {
    return "父类实例方法";
  }
}

class SmsChannel extends ParentChannel {
  String label = "子类字段";

  static String kind() {
    return "子类静态方法";
  }

  @Override
  String message() {
    // 此处 super.message() 得到“父类实例方法”
    return "子类实例方法";
  }
}

成员访问实验

用 Tab 键也可切换
表达式结果

“父类字段”

1编译期选择

按引用声明类型 ParentChannel 查找字段 label,因此绑定 ParentChannel.label。

2运行期处理

字段不参与动态分派;即使对象是 SmsChannel,也不会改查子类字段。

判定:字段访问由引用声明类型决定,字段隐藏不是方法重写。

重载与重写是连续的两次决策

如果同名方法存在多个参数列表,编译器先依据引用声明类型、实参的编译期类型选出方法签名,这一步是重载解析。签名确定后,若它是可重写实例方法,运行时才依据实际对象选择最终实现。

因此,“对象是子类”不会让编译器凭空看到只声明在子类中的重载版本。

ParentChannel ref = new SmsChannel();
ref.message("你好");
  1. 假设父类声明 message(Object),子类重写它,并另行重载 message(String)
  2. 编译期只从 ParentChannel 可见成员中选中 message(Object)
  3. 运行期沿这个已选签名分派,调用 SmsChannel.message(Object);不会改选 message(String)

即时判断

先判断,再看绑定阶段是否与你的理由一致。

1. ParentChannel ref = new SmsChannel(); 后,ref.label 会读取“子类字段”。

2. 同一赋值下,ref.message() 会在运行时调用 SmsChannel 的重写实现。

3. 重载与重写都只由运行时对象类型决定。