如何通过静态方法的重定义 Hiding 实战区分其与方法重写的本质差异
静态方法不能被重写,只能被隐藏,其调用取决于引用变量的声明类型,在编译期绑定。实例方法可被真正重写,调用由运行时对象的实际类型决定,实现动态多态。两者核心差异在于方法绑定的时机与依据,静态方法不参与运行时多态机制。
在Ja va的世界里,多态无疑是面向对象编程最迷人的特性之一。它让代码拥有了“动态”的灵魂,但这份灵动也带来了不少困惑。其中,一个经典且高频的混淆点,就出现在静态方法与实例方法的重写行为上。很多开发者会误以为两者遵循相同的规则,实则不然,这背后是编译期与运行时的根本差异。
简单来说,静态方法不能被重写,只能被“隐藏”(Hiding);而实例方法才能被真正重写(Overriding)。理解这一点,关键在于抓住方法绑定的时机和调用依据。
静态方法不能被重写,只能被隐藏;因其绑定发生在编译期,依赖引用的声明类型而非对象的实际类型,不参与虚方法表和运行时多态机制。

静态方法的隐藏(Hiding)是怎么发生的
当子类定义了一个与父类签名完全相同的静态方法(同名、同参数、同返回类型)时,会发生什么?它并不会像实例方法那样覆盖父类方法,而是将父类的静态方法“隐藏”起来。这意味着:
- 调用哪个版本,完全取决于引用变量的声明类型,与实际指向的对象类型无关。
- 这个过程没有运行时多态的参与,是纯粹的编译期决策。
- 父类的原始静态方法依然存在,你仍然可以通过
父类名.方法名()的方式显式调用它。
实例方法的重写(Overriding)才是真正的多态
相比之下,当子类重写一个非 private、非 static 的实例方法时,情况就完全不同了:
- 调用哪个版本,由运行时对象的实际类型决定。
- 即使引用变量的类型是父类,只要它实际指向一个子类实例,就会执行子类重写后的方法。
- 这就是动态绑定,是支撑面向对象核心特性——多态——的基石。
一段对比代码就能看清区别
理论说再多,不如一行代码来得直观。假设我们有父类 A 和子类 B extends A,它们都定义了静态方法 test() 和实例方法 run()。
A a = new B(); a.test(); // 输出 A.test() —— 静态方法:看左边 a 是 A 类型 a.run(); // 输出 B.run() —— 实例方法:看右边 new B(),真正重写生效
看到了吗?同样的引用a,调用静态方法时执行了父类版本,调用实例方法时却执行了子类版本。这就是“声明类型”与“实际类型”在两种方法调用上的分野。
如果再换一种写法:B b = new B(); b.test();,那么输出就会是B.test(),因为此时的声明类型是B。这再次印证了,静态方法的调用路径不依赖于对象本身,只依赖于引用的声明类型。
为什么 private 和 static 方法都不能重写
其实,private方法和static方法在“不可被重写”这一点上,本质原因是相通的:它们都不参与运行时的动态分派机制。
- private 方法:它对子类根本不可见,连继承都没有发生,自然就谈不上重写。子类中定义同名方法,那完全是一个独立的新方法。
- static 方法:它属于类本身,而非任何对象实例。它的绑定在编译期就已经确定,JVM在调用时不会去查询对象的虚方法表(vtable)。
所以说,子类虽然可以定义与父类private或static方法同名的方法,但这只是一种形式上的“定义”,并非面向对象意义上的“重写”或“覆盖”。理解了这个底层机制,下次再遇到类似问题,你就能一眼看穿本质了。


































