多态是面向对象最重要的特征。具体到Java中是如何体现的呢。
多态在我们的使用中其实就是重载与重写。下面分别进行讲述一下。
重载
重载的定义:一个类中,如果有两个方法的方法名相同,但参数列表不同,可以说一个方法是另一个方法的重载。
注意2点:1.方法名相同 2.参数列表不同(参数列表为:参数的类型,参数的个数)
调用重载方法的时候,JVM会根据不同的参数列表来选择合适的方法。
我们来看一下代码:
public class Main { public static void main(String[] args) { Father f= new Father(); f.print(); f.print(5); } } class Father{public void print() { System.out.println("father"); }public void print(int x) { System.out.println(x); } }
看一下字节码:
可以很明显看到invokevirtual调用了不同的方法。
重写
当子类拥有和父类相同的方法(方法名,参数列表相同),则说子类重写了父类的方法。
注:1.方法名相同 2.参数列表相同 3 子类重写方法的访问修饰符必须大于等于父类的方法。(比如父类为 protected void print(){},则子类必须protected或者public)
来看一下重写的例子:
public class Main { public static void main(String[] args) { Father son = new Son(); son.print(); } } class Father{public void print() { System.out.println("father"); } } class Son extends Father{public void print() { System.out.println("son"); } }
结果很明显:son
看一下字节码:
从第9行可以看到:invokevirtual指令的注释显示了是Father.print()的符号引用。但是为什么结果是son而不是father呢。
首先要说关于静态类型和实际类型:
Father son = new Son(); 中
Father称为静态类型,而Son称为实际类型。区别在于静态类型在编译期就会确定,而实际类型要在运行期才能确定。编译器编译程序时候并不知道对象的实际类型是什么。
所以由于重载在编译期就确定了类型。所以这个过程也可以叫静态分派。而重写是动态分派。
来看一下invokevirtual指令的解析过程(参考《深入理解Java虚拟机》):
1.找到操作数栈顶的第一个元素所指向的对象的实际类型。记作C(在上面代码实际类型为Son)
2.如果在类型C中找到与常量中描述符合简单名称都相符的方法,则进行访问权限校验,如果通过则返回这个方法的直接引用。查找过程结束。不通过的话则返回java.lang.IllegalAccessError异常
(上面代码指的就是查找print()方法,在Son中找到了。所以就成功结束查找过程)
3.否则,按照继承关系从下往上一次对C的各个父类进行第2步的搜索和验证过程。
4.如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常。
由于invokevirtual指令执行的第一步就是在运行期确定实际类型。所以invokevirtual指令把常量池的方法的类方法符号引用(即print())解析到了Son的直接引用中。所以我们具体看到的结果就是Son.print()。
这就是重写的本质。
关于Father f = new Son() 中 f的方法问题。
在题目中经常会看到关于下面的题目:
public class Main { public static void main(String[] args) { Father son = new Son(); son.print(); } } class Father{public void print() { System.out.println("father"); } } class Son extends Father{public void printSon() { System.out.println("son"); } }
结果大家都能记住:father
这里就可以用invokevirtual指令的解析顺序来解释:C还是Son类,但是Son中并没有print()方法,所以往父类找。在父类找到了返回print()方法的直接引用。所以最后调用的是Father.print()。
注:以前看视频这个是父类引用指向子类的实例对象。向上转型之类的,列举一堆例子概念让人记住结果而已。反正我现在已经不怎么记得了。
当从invokevirtual指令解析顺序去理解,其实整个过程会变得清晰多了。
来源:https://www.cnblogs.com/fabaogege/p/10358507.html