Java多态学习笔记:从入门到精通

Java多态要求方法为virtual(非static、非final、非private),private方法因不可继承且编译期绑定,无法参与多态;static方法属类级别,基于声明类型静态绑定;default方法支持多态但有优先级规则;滥用instanceof违背多态设计原则。

Java 多态不是“写个父类引用指向子类对象”就完事了,它真正起作用的前提是:方法必须是 virtual(即非 static、非 final、非 private),且在运行时能被动态绑定。否则看似多态,实则静态绑定或编译报错。

为什么 private 方法不能参与多态

private 方法无法被继承,子类里同名方法只是新定义的独立方法,和父类毫无关系。JVM 在编译期就直接绑定到声明该方法的类,根本不会进虚方法表(vtable)。

class Animal {
    private void speak() { System.out.println("Animal sound"); }
    public void talk() { speak(); } // 这里调用的是 Animal.speak()
}
class Dog extends Animal {
    private void speak() { System.out.println("Woof!"); } // 不是重写,是全新方法
}
// new Dog().talk() 输出仍是 "Animal sound"
  • private 方法调用永远基于**编译时类型**,和实际对象无关
  • 想让子类定制行为?改用 protected + final 模板方法,或直接用 public/protected 抽象/可重写方法
  • IDE 通常会标灰子类中的 “重写” private 方法,提示它未覆盖任何东西

static 方法看起来像多态,其实只是“语法糖”

static 方法属于类,不依赖实例,JVM 绑定依据是**引用变量的声明类型**,而非实际 new 出来的类型。这叫“静态绑定”,和多态无关。

class Shape { static void draw() { System.out.println("Shape.draw"); } }
class Circle extends Shape { static void draw() { System.out.println("Circle.draw"); } }

Shape s = new Circle();
s.draw(); // 输出 "Shape.draw",不是 "Circle.draw"
  • 哪怕写成 ((Circle)s).draw(),只要声明类型是 Shape,仍调用 Shape.draw()
  • 真正想按子类行为执行?必须显式用子类名调用:Circle.draw()
  • 混淆点常出现在面试题中——误把“重载”或“隐藏(hiding)”当成“重写(overriding)”

接口默认方法(default)和多态的关系

接口 default 方法支持多态,但有优先级规则:类中实现的方法 > 接口 default 方法;多个接口冲突时,必须在实现类中显式重写。

interface Flyable { default void fly() { System.out.println("Flap wings"); } }
interface Swimmable { default void fly() { System.out.println("Dive and glide"); } }
class Duck implements Flyable, Swimmable {
    public void fly() { System.out.println("Duck flies with style"); } // 必须实现,否则编译失败
}
  • 如果只实现一个接口(如仅 Flyable),new Duck().fly() 走的是 Flyable.fly()
  • 子类若未重写 default 方法,且父类已有同签名实例方法,则优先调用父类方法(不是接口的 default
  • 别指望靠 default 方法绕过抽象类设计——它不能访问实现类的 this 非公开字段,能力受限

运行时类型判断:别滥用 instanceof 和强制转型

频繁用 instanceof + cast 往往说明设计违背了多态本意。理想情况是:通过统一方法签名让各子类自行决定行为,而不是外部判断类型再分支处理。

  • 反模式:if (obj instanceof Dog) ((Dog)obj).bark(); else if (obj instanceof Cat) ((Cat)obj).meow();
  • 正解:在父类定义 makeSound(),各子类重写,调用 obj.makeSound() 即可
  • 真需要类型判断?只在集成点(如反序列化、插件加载)或调试日志中出现;生产逻辑中应尽量收敛到策略模式或访问者模式
  • instanceof 对泛型类型擦除后无效:list instance

    of ArrayList
    编译不通过,只能写 list instanceof ArrayList

多态的复杂性不在语法,而在责任划分:哪些行为该由调用方决定,哪些该由对象自身封装。一旦开始在外部用 if-else 拆解类型,就要停下来问一句——这个逻辑,真的不属于对象内部吗?