static方法是编译期绑定,即调用目标由代码中类名或变量声明类型决定,与运行时对象实际类型无关;编译器直接生成invokestatic指令,不进行动态分派,无多态性,仅支持隐藏而非重写。

Static修饰的方法在编译期就完成绑定,意思是:调用哪个方法,**不看运行时的对象实际类型,只看代码里写的是哪个类名(或变量声明类型)**,编译器在编译阶段就能确定调用目标,直接写进字节码,运行时不再查表、不走虚方法分派。
为什么叫“编译期绑定”?
因为Java编译器(javac)看到 ClassName.staticMethod() 或 ref.staticMethod()(其中 ref 是 static 方法所在类的引用),会立刻根据类名和方法签名,在常量池中生成一个 invokestatic 指令,并把目标方法的符号引用固化下来。这个决定在 .class 文件生成时就完成了,JVM 运行时照着执行就行,不检查对象实例、不考虑继承重写、也不做动态查找。
和非 static 方法的关键区别
- 没有多态性:子类可以定义同名 static 方法,但这不是重写(override),只是隐藏(hiding)。调用时始终按“点号左边的类名”来选方法,哪怕你用子类实例去调父类 static 方法,也还是执行父类的版本。
- 不依赖对象实例:static 方法压根不需要 this,所以即使传 null 进去也不会空指针(只要调用语法合法)。例如 ((String)null).valueOf(123) 能正常运行,因为编译后实际调用的是 String.valueOf(123),null 根本没参与方法分派。
- 无法被 override,只能被 hide:你在子类里写一个和父类签名相同的 static 方法,编译器不会报错,但两个方法完全独立。调用 Parent.staticM() 就执行父类的,调用 Child.staticM() 就执行子类的——区别只在你写的类名,不在运行时对象类型。
一个小例子帮你看清绑定时机
假设有:
class A { static void m() { System.out.println("A.m"); } }
class B extends A { static void m() { System.out.println("B.m"); } }
然后写:
A a = new B(); a.m(); // 编译期就定为 A.m —— 输出 "A.m"
注意:这里 a 的声明类型是 A,所以编译器生成 invokestatic A.m;即使 a 实际指向 B 的实例,也完全无关。如果你写 B b = new B(); b.m();,编译器就生成 invokestatic B.m。两段代码生成的字节码指令不同,区别在编译时就固定了。
延伸理解:它和 final、private 方法的绑定类似,但原因不同
final 和 private 方法也是编译期绑定(使用 invokestatic 或 invokespecial),但那是出于语义限制(不能重写);而 static 方法是因设计上就脱离实例,天然不具备运行时多态基础。它们的共同点是:JVM 不需要在运行时通过对象的实际类型去查虚方法表(vtable)。










