静态方法不能被重写,只能被隐藏;调用取决于编译时引用类型而非运行时对象类型,如parent p = new child(); p.staticmethod()执行parent版本。

静态方法不能被重写,只能被隐藏
Java里没有“静态方法重写”这回事。子类定义一个和父类签名完全相同的static方法时,不是覆盖(override),而是隐藏(hiding)。调用哪个版本,**取决于编译时的引用类型,而不是运行时的实际对象类型**。
常见错误现象:Parent p = new Child(); p.staticMethod(); 调用的是Parent.staticMethod(),不是Child里的——哪怕p实际指向Child实例。
- 使用场景:多用于工具类继承时提供同名但语义更具体的静态入口,比如
StringUtils子类想加个formatForInternalUse(),但不能靠多态自动分发 - 参数差异:签名必须完全一致(包括返回类型、参数列表、
static修饰符),否则就是新方法,不构成隐藏 - 性能影响:无额外开销,因为绑定在编译期完成;但容易让人误以为是动态绑定,引发逻辑 bug
实例方法重写(Override)和静态方法隐藏(Hiding)的关键区别
核心就一条:重写看对象,隐藏看引用。
示例对比:
立即学习“Java免费学习笔记(深入)”;
class A { static void m() { System.out.println("A.m"); } void n() { System.out.println("A.n"); } }
class B extends A { static void m() { System.out.println("B.m"); } void n() { System.out.println("B.n"); } }
A a1 = new B();
a1.m(); // 输出 "A.m" —— 静态方法,按 a1 的声明类型 A 找
a1.n(); // 输出 "B.n" —— 实例方法,按 new B() 的实际类型找
- 编译期检查:隐藏不要求
@Override注解,加了会报错;重写必须可加(且推荐加),否则可能因签名不符而意外变成新方法 - 访问控制:隐藏方法可以比父类更宽松(如父类
protected,子类public),重写则不能更严格 - 异常声明:隐藏对
throws无限制;重写不能抛出父类未声明的受检异常
为什么编译器不阻止你在子类里“重写”静态方法
因为语法上完全合法——静态方法本来就不参与多态,JVM 层面也没有 vtable 查找机制。它只是“恰好同名”,编译器只做符号解析,不做强制约束。
容易踩的坑:
- 把隐藏当重写用,结果测试时用
Child c = new Child(); c.staticMethod();看起来正常,一换成Parent p = new Child(); p.staticMethod();就出错 - IDE 提示“Method hides a static method in superclass”,很多人直接点“suppress”忽略,埋下隐患
- 单元测试若只用具体类型调用,根本测不出隐藏导致的逻辑偏差
什么时候该用隐藏,什么时候该重构
隐藏不是设计模式,而是语言限制下的妥协。真需要行为差异化,优先考虑实例方法 + 多态;静态方法适合无状态、与类型无关的操作。
- 适合隐藏的场景:子类提供同名静态工具方法,但实现细节不同,且调用方明确知道用哪个类名调用(如
JsonUtil.parse()vsXmlUtil.parse()) - 该重构的情况:如果发现总要根据对象类型决定调用哪个静态方法,说明它本该是实例方法——把逻辑移到
parse(String)实例方法里,再让子类重写 - 替代方案:用工厂方法或策略接口,避免在静态层做分支判断
最常被忽略的一点:静态方法隐藏不会触发任何运行时多态机制,所以它和“继承”在语义上其实是割裂的——你写的不是“子类扩展父类行为”,只是“碰巧起了个一样的名字”。这点在代码审查时极难察觉。








