多态数组必须声明为父类或接口类型,如Person[],运行时存入子类对象以实现动态绑定;调用时仅限父类声明的成员,访问子类特有成员需instanceof检查后向下转型;优先使用List等泛型集合提升类型安全。

多态数组的声明和初始化必须用父类/接口类型
Java中实现多态数组,核心是「声明时用父类或接口类型,运行时存入子类对象」。如果直接声明为 Student[] 或 Teacher[],就失去了多态性——编译器只认具体类型,无法在运行时动态调用不同子类的重写方法。
正确做法是统一用公共父类(如 Person)或接口(如 Runnable)作为数组元素类型:
Person[] people = new Person[3];
people[0] = new Student("张三", 20);
people[1] = new Teacher("李四", 45);
people[2] = new Staff("王五", 38);
这样后续调用 people[i].work() 才能触发 JVM 的动态绑定(virtual method invocation),实际执行的是各子类重写的 work() 方法。
调用时不能直接访问子类特有成员
数组元素被当作 Person 类型看待,编译器只允许调用 Person 中声明过的方法或字段。哪怕数组里实际存的是 Student 对象,也不能直接写 people[0].studentId——这会编译报错:cannot find symbol。
立即学习“Java免费学习笔记(深入)”;
若确实需要访问子类特有功能,必须显式向下转型,且要加 instanceof 安全检查:
if (people[i] instanceof Student) { ((Student) people[i]).enrollCourse("Java"); }- 漏掉
instanceof可能抛ClassCastException - 频繁转型说明设计可能有问题,优先考虑把共性行为上提到父类或接口
数组本身不是泛型,但可用泛型集合替代增强类型安全
Person[] 是原始类型数组,不检查运行时插入的对象是否真为 Person 子类(比如误写 people[0] = "hello" 会编译失败,但 people[0] = new Dog() 如果 Dog 也继承 Person 就合法——而你可能并不想让它进这个数组)。
更现代、更安全的做法是用泛型集合:
Listpeople = new ArrayList<>(); people.add(new Student("张三", 20)); // 编译期就确保只能加 Person 及其子类 people.add(new String("oops")); // 编译错误
注意:泛型在运行时被擦除,但编译期检查已大幅降低类型错误风险;而数组的类型检查发生在运行时(ArrayStoreException),晚于开发反馈。
动态绑定生效的前提是方法被正确重写(override),不是重载(overload)
常见误区:在子类里写了跟父类签名相似但参数不同的方法,以为能多态调用。例如:
- 父类
void work() {},子类写void work(String task) {}→ 这是重载,不会参与动态绑定 - 必须是相同方法名、相同参数列表、兼容返回类型、非
static/final/private→ 才算重写 - 加上
@Override注解,编译器会帮你校验是否真构成重写
没加 @Override 却写错了签名,表面看似“多态”,实则调用的永远是父类版本,问题很难排查。
@Override 和 instanceof 把这些边界显式标出来。










