在学习的路上,我们的困难总是不断,我们学习总是伴随着错误和收获,错误能帮助我们发现自己的不足,而收获能激励我们走向更高远的地方,记录自己的错误和收获,是一种复盘和总结,能够让自己对自己所学的知识掌握的更牢固。
我在这里记录一下我学习过程中的发现和感悟,希望能对与正在看这篇文章的你有所帮助,同时,我也欢迎大家帮忙指正不对的地方,相互提高:
1.方法签名
一个方法的
方法名称和参数列表被称为一个方法的签名,当父类和子类的方法,如果两个方法的方法名称和参数列表都相同的时候,签名就一样了,计算机中的签名是唯一标识,不能重复,所以不能存在两个签名一样的方法,这个时候子类新产生的方法就会把父类中的方法覆盖掉(@Override),保证签名的唯一性,覆盖之后我们产生子类的实例化对象时就只会有覆写之后的方法。那么如果现在要在子类内部用super调用父类被重写的方法的话也可以成功,为什么?,因为super是直接调用父类中的方法,并不是子类中有这个方法(我曾经被这个问题困扰过很久,现在想想简直太蠢)。
在《Java继承机制探索:向上转型》1.2中,我解释了向上转型的 “原理”,其实是我根据自己的学习和感悟想出来的,就是继承后的子类在实例化的时候会产生多层次的实例化空间,而类型引用指向相对应的实例化空间,我用了自己的一个理解模型(姑且称为引用模型)来说明了为什么向父类转型之后只能调用父类的属性和未被重写的方法,但是这个模型我还没有找到证据证明它可行,所以仅提供一个参考,帮助理解,下面来说明为什么会只能访问父类属性和未被覆写的方法以及方法覆写之后为什么只能执行被子类覆写的方法。
2.(普通、重载)方法调用的执行过程:
Java编译器在调用方法的时候会先查找这个类中有没有方法名相同的方法,再查找它的父类中有没有同名的方法,如果有就把方法都加入到候选区。如:Student类继承自Person类:
class Person {
......
public void move(){...}
public void sleep(){...}
public void eat(){...}
public void eat(String str){...}
......
}
class Student {
......
public void move(){...}
public void sleep(){...}
public void eat(){...}
public void eat(String str, int x){...}
......
}
如果我们现在有一个Client类,产生一个Student实例化对象,并调用其中的eat()方法:
class Client {
public static void main(String[] args){
Person p = new Student();
String food = "桃子peach";
p.eat(food,3);
}
}
当程序运行到 p.eat();的时候,编译器会在Student类中寻找所有方法名为 eat 的方法,加入候选区,再寻找Student的父类Person中所有非private的 eat 方法,加入候选区,这个时候编译器就获得了所有方法名为 eat 的方法{ Student.eat()、Student.eat(String str,int x)、Person.eat()、Person.eat(String str) },再根据调用方法的参数列表来进行比对,发现调用的参数列表是(String str, int x),便调用 Student.eat(String str, int x)这个方法。如果没有找到与参数类型相匹配的方法,那么编译器就会报错,这个过程叫 “重载解析” 。
这里还有另一种情况,因为在重载解析过程中允许类型转换,如int类型可以转换为double类型,Student类型可以转换为Person类型,所以当类型转换之后,有多个方法的参数列表满足调用方法的参数列表的话,也会报错(我不知道我是否讲明白了,参考自《Java核心技术 卷一》第5.1.3节)
3.方法绑定
3.1 静态绑定:
一个类中如果有用 private、static、final 定义的方法,那么在编译期的时候编译器就可以准确的知道这个方法应该由谁调用。这些关键字定义的方法会始终和这个类绑定在一起:
private方法继承后无法被子类访问,相当于无法继承;static方法在一个类中是固定的,可以继承可以被屏蔽但无法被覆写;final方法可以继承,不可更改,无法覆写。
Java编译器编译的时候就把以这些关键字定义的方法与类本身进行了绑定,要调用这些方法的时候JVM就能根据绑定找到并调用这些方法,这种调用机制就称作静态绑定。
3.2.动态绑定:为什么子类覆写父类的函数之后执行的都是子类覆写的函数?
除了由private、final、static 所修饰的方法和构造方法外,JVM在运行期间决定方法由哪个对象调用的过程称为动态绑定。
Java继承发生之后如果进行了方法的覆写,那么就会发生动态绑定:
class Father {
public void run(){...}
}
class Son {
@Override
public void run(){...}
public static void main(String[] args){
Father son = new Son();
son.run();
}
}
Son 类在实例化的过程中会调用父类 Father 的构造方法,而父类构造器会把父类的方法加载到内存中,而轮到子类构造器执行时,由于 run 方法签名相同,为了保证签名唯一性,就会把已经加载到内存中的 run 方法给替换掉,换成新的 run 方法,原来的 run 方法在子类实例化空间中就不存在了,所以只能调用子类重写的 run 方法。
站在JVM的角度来看,Father son = new Son();在编译的时候JVM并不知道 son 到底引用的是什么类型的实例,之后当执行的时候才知道 son 引用的是 Son 类型实例,所以才进行调用 Son 中的方法。因为这个是在程序源代码被编译之后在运行时期发生的,所以被叫做动态绑定。
CopyRight @Kirito ——转载请注明出处