从Java继承类的重名static函数浅谈解析调用与分派

今天被实习生问了这么个问题:

在java中,static成员函数是否可以被重写呢?

结论是,你可以在子类中重写一个static函数,但是这个函数并不能像正常的非static函数那样运行。

也就是说,虽然你可以定义一个重写函数,但是该函数没有多态特性。让我们测试一下:

 1 class  testClass1{
2 static void SMethod(){
3 System.out.println("static in testClass1");
4 }
5 }
6 class testClass2 extends testClass1{
7 static void SMethod(){
8 System.out.println("static in testClass2");
9 }
10 }
11 public class MainClass{
12 public static void main(String... args){
13 testClass1 tc1=new testClass2();
14 testClass2 tc2 =new testClass2();
15 tc1.SMethod(); //输出结果为 static in testClass1
16 tc2.SMethod(); //输出结果为 static in testClass2
17 }
18 }

从结果中可以看到,当我们用父类的实例引用(实际上该实例是一个子类)调用static函数时,调用的是父类的static函数。

原因在于方法被加载的顺序。

当一个方法被调用时,JVM首先检查其是不是类方法。如果是,则直接从调用该方法引用变量所属类中找到该方法并执行,而不再确定它是否被重写(覆盖)。如果不是,才会去进行其它操作(例如动态方法查询)

可能有的人一拍大腿,这不就是java的静态/动态分派么!

有点像,但还真不是,静态分派与动态分派是用来确定重载和重写逻辑的。在重载过程中,编译器根据方法参数的静态类型(比如tc1的静态类型是class1,tc2的是class2,但本文这里不是重载!)来确定使用方法的版本,这叫做静态分派。动态分派是用于方法重写的,比如我调用一个类A的方法f,如果该类有子类a,那么我以a来调用f的时候,调用的实际是a.f而非A.f。

看起来还真的像动态分派是不是?但是结果不符合啊!

这里的原因在于,动态分派时,我们实际是在讨论Java的invokevirtual指令的行为:这个指令首先会去寻找调用者的运行时类型,然后在其方法表里面寻找匹配的方法,如果找不到,再从其父类里找。这个过程就是Java中方法重写的本质,也就是动态分派。

而static方法是通过invokestatic指令来调用的。由于static方法是一种编译期可知,运行期不可变的方法,所以尽管子类和父类都有同样的方法名,而事实上它们是不同的方法,也是完全可以区分的方法。在调用static方法时,编译器就会直接在类加载时把其符号引用解析为直接引用,不存在说子类找不到方法之后再去父类找这种行为,所以也叫解析调用。

这就是上面的例子中看起来像是重写的方法却没有产生重写的效果的原因。

全文完。

上一篇:Softmax 损失-梯度计算


下一篇:Heritrix源码分析(三) 修改配置文件order.xml加快你的抓取速度(转)