
静态方法不能被覆盖,只能被隐藏
Java里没有“静态方法覆盖”这回事——override只适用于实例方法。子类声明同名、同参数、同返回类型的static方法时,实际是**隐藏(hiding)**了父类方法,不是重写。
关键区别在于:调用哪个版本,取决于**引用类型**(编译时类型),而不是对象实际类型(运行时类型)。
- 如果用
Parent p = new Child();,再调用p.staticMethod(),执行的是Parent.staticMethod() - 如果用
Child c = new Child();,再调用c.staticMethod(),执行的是Child.staticMethod() - 哪怕子类方法加了
@Override注解,编译器也会报错:method does not override or implement a method from a supertype
实例方法调用看运行时类型,静态方法看编译时类型
这是最常混淆的点。多态性对静态方法完全不生效——JVM在编译期就绑定了调用目标,不经过虚方法表查找。
比如:
立即学习“Java免费学习笔记(深入)”;
class Parent { static void say() { System.out.println("Parent"); } }
class Child extends Parent { static void say() { System.out.println("Child"); } }
Parent p = new Child();
p.say(); // 输出 "Parent" —— 看 p 的声明类型(Parent)
Child c = new Child();
c.say(); // 输出 "Child" —— 看 c 的声明类型(Child)
- 把
p强制转成(Child)p再调用say(),输出仍是"Parent",因为转型不改变变量的编译时类型 - 想让
p调用子类静态方法?必须显式写Child.say(),不能靠多态 - IDE 通常不会提示“可安全重构为多态调用”,因为根本不可行
为什么设计成这样?和类加载、符号解析强相关
静态方法属于类本身,绑定发生在类加载的解析阶段;而实例方法绑定依赖对象头里的vtable,在运行时才确定。
- 类加载时,JVM 遇到
invokestatic指令,直接根据字节码里的类符号(如Parent.say)定位方法,不查继承关系 - 如果允许“覆盖”,就得在每次调用时检查子类是否重定义该静态方法——这违背静态方法“早绑定、快调用”的设计初衷
-
final static方法无法被隐藏(编译报错),但private static可以被子类同名方法“遮蔽”,只是语义上不算隐藏(因不可见)
容易踩坑的典型场景
最危险的是误以为“看起来像多态,就真是多态”。尤其在工具类继承、测试 mock、或泛型擦除后类型丢失时。
- 写了个
Utils基类带static parse(),子类JsonUtils也写了同签名static parse(),结果所有Utils.parse(...)调用都走基类逻辑 - 单元测试里用
Mockito.mock(Child.class),再调用静态方法——mock 对静态方法完全无效,仍走真实类逻辑 - 反射调用
Class.getMethod("say")时,传Parent.class还是Child.class,拿到的是不同方法对象,别指望getDeclaredMethod自动向上找 - Android 开发中混淆(ProGuard/R8)可能删掉未直接引用的静态方法,隐藏后的方法若没被显式调用,容易被误删
真正需要“可替换行为”的地方,别用静态方法——改用实例方法 + 依赖注入,或者策略接口 + 工厂。静态方法的“隐藏”不是设计优势,是妥协下的明确限制。








