浅淡java中的重载重写

java具备面向对象的3个基本特征,继承、封装、多态。重载、重写是多态特征的一些最基本的表现,本文我们简单聊一下重载和重写

重载

示例一

先看一下重载的示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.bk.exercise.base;

/**
* @author BK
* @description:
* @date 2019-08-19 00:52
*/
public class OverLoadTest {
static abstract class Fruit{
}
static class Apple extends Fruit{
}
static class Banana extends Fruit{
}
public void eat (Fruit fruit){
System.out.println("eat fruit .");
}
public void eat (Apple fruit){
System.out.println("eat apple .");
}
public void eat (Banana fruit){
System.out.println("eat banana .");
}

public static void main(String[] args) {
Fruit apple = new Apple();
Fruit banana = new Banana();
OverLoadTest test = new OverLoadTest();
test.eat(apple);
test.eat(banana);
}
}

大家可以先停下来,想一想运行之后的结果是什么?

在OverLoadTest test固定的前提下, test会选择哪个重载版本呢,他的选择依据是什么?

解析

在上述代码中Fruit apple = new Apple(); Fruit是静态类型,而Apple是动态类型

test会选择哪个,取决于入参的数量和数据类型,虽然代码中定义了两种静态类型动态类型不同的变量,但编译器在重载时是通过参数的静态类型是而不是动态类型作为判断依据的,并且静态类型在编译期就是可以知道的,因此在编译阶段Javac就已经根据参数决定使用哪个重载版本了,这种根据静态类型判断方法的执行版本叫做静态分配重载就是静态分配的典型应用。

运行结果

1
2
eat fruit .
eat fruit .

其它情况

大多数情况下,重载版本并不是唯一 的,往往很难确定一个“合适的版本”,静态类型只能通过语言上的规则去理解和推断

重写

示例二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.bk.exercise.base;

/**
* @author BK
* @description:
* @date 2019-08-19 01:16
*/
public class OverRide {
static abstract class Fruit{
public abstract void eat ();
}
static class Apple extends Fruit {
public void eat() {
System.out.println("eat apple .");
}
}
static class Banana extends Fruit {
public void eat() {
System.out.println("eat banana .");
}
}
public static void main(String[] args) {
Fruit apple = new Apple();
Fruit banana = new Banana();
apple.eat();
banana.eat();
}
}

运行之后的结果如下

1
2
eat apple .
eat banana .

这个结果不会出乎意料,对于习惯了面向对象思维的Java程序员觉得这样的结果是理所当然的,但是JVM是如何知道要调用哪个方法的?这个就和JVM动态分配有关

invokevirtual指令的多态查询过程

  1. 找到操作数栈顶的第一个元素所指向的对象的实际类型A
  2. 如果在类型A中找到与常量中的描述符和简单名称都相符的方法,并且权限访问验证通过,则调用这个方法的直接引用,如果不通过,则抛出IllegalAccessError异常
  3. 如果没有找到与常量中的描述符和简单名称都相符的方法,则按继承关系从下往上依次对A的各个父类进行第2步的搜索和验证过程
  4. 如果不有找到合适的方法,则抛出java.lang.AbstractMethodError异常

由于invokevirtual指令执行的第一步就是在运行期间确定接收者的实际类型,所以两调用中invokevirtual指令把常量池中的类型方法符号引用解析到了不同的直接引用上,这个过程就是Java语言方法重载的本质。我们把这种在运行期根据实际类型确定方法版本的分派过程称为动态分派

JVM动态分配的实现

JVM的动态分派实现是在类在的方法区中建立一个虚方法表(vtable),vtable中存放着各个方法的实际入口

如果某方法在子类中没有被重写,那子类的vtable和父类vtable里面的地址入口都是指向父类的入口

如果某方法在子类中重写了,子类方法表中的地址将会被替换为指向子类实现版本的入口地址。

在JVM选择调用哪个方法的时候就是先查询动态类型的vtable和父类型的vtable。

总结

重载是静态的,重写是动态的

BK wechat
扫一扫,用手机访问本站