Java的多态方法的与静态与动态分派

前言

  • 多态:一个对象变量可以引用多个实际对象
  • 动态绑定:程序运行自动选择调用那个对象的方法

    1 先看C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include 
using namespace std;
class Person{
public:int x=1,y=1;
void Show() {
cout << "I am a Person " <<x<<y<< endl;
}
};
class Student:public Person{
public:void Show() {
cout << "I am a Student " <<x<<y<< endl;
}
};
int main(){
Student s0;
Person p0=s0;
p0.Show();
s0.Show();
return 0;
}
  • 运行结果

1
2
I am a Person 11
I am a Student 11

2.再看Java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

class Person{
public void Show(){
System.out.println("I am a Person");
}
}

class Student extends Person{
public void Show(){
System.out.println("I am a Student");
}
}

public class Main{
public static void main(String arg[]){
Student s0=new Student();
Person p0=s0;
p0.Show();
s0.Show();
}
}
  • 运行结果

1
2
I am a Student
I am a Student

3.分析原因

Java与C的绑定不同。Java中只有final、static、private和构造方法是静态绑定的,其它所有方法都采用动态绑定,而C++只有虚函数进行的是动态绑定。
所以在C++中,p0.Show()才用静态绑定的Show(),尽管指向的是Student。而Java则是动态绑定,因为有JVM,JVM会调用变量实际指向的对象的方法。p0实际是指向s0的,所以会调用s0的Show()。

  • 从java的字节码角度出发,有5种调用指令
  • invokeinterface: 调用接口中的方法,实际上是在运行期决定实际实现该接口的哪一个对象
  • invokestatic 调用静态方法
  • invokespecial 调用实际的私有方法,构造方法
  • invokevirtual 调用虚方法 运行期间动态查找的过程
  • invokedynamic 动态调用
  • 方法重载时一种静态类型,调用的时候就是声明的类型,编译时可以确定。
  • 方法重写是一种动态类型,是实际调用的时候,才知道的。需要运行时确定。
  • 虚拟机一定调用引用对象的”实际类“的最适合的方法。虚拟机预先为每一个类创建一个

方法表,在运行时,虚拟机会查这个表。首先适配实际类的方法,否则在超类中继续寻找,以此类推。

字节码带来的疑惑?

  • INVOKEVIRTUAL Person.Show ()V //—这里明明显示的是调用Person.Show怎么调用有到 Student.Show

    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
    // access flags 0x9
    public static main([Ljava/lang/String;)V
    L0
    LINENUMBER 15 L0
    NEW Student
    DUP
    INVOKESPECIAL Student.<init> ()V
    ASTORE 1
    L1
    LINENUMBER 16 L1
    ALOAD 1
    ASTORE 2
    L2
    LINENUMBER 17 L2
    ALOAD 2
    INVOKEVIRTUAL Person.Show ()V //---这里明明显示的是调用Person.Show怎么调用有到 Student.Show
    L3
    LINENUMBER 18 L3
    ALOAD 1
    INVOKEVIRTUAL Student.Show ()V
    L4
    LINENUMBER 19 L4
    RETURN
    L5
    LOCALVARIABLE arg [Ljava/lang/String; L0 L5 0
    LOCALVARIABLE s0 LStudent; L1 L5 1
    LOCALVARIABLE p0 LPerson; L2 L5 2
    MAXSTACK = 2
    MAXLOCALS = 3
    }
  • 这个就涉及到原理INVOKEVIRTUAL了

INVOKEVIRTUAL

  • 首先去找在操作数的栈顶的实际指向的那个对象的实际类型,寻找到描述和访问权限都符合,则直接返回这个方法的直接引用。
  • 如果找不到,然后去找父类
  • 再找不到,抛异常

这样依次去找会不会很慢?

  • JVM会优化,生成一个叫vtable的方法表。
  • 通过vtable代替实际查找。