【198期】面试官:你能说出 方法重载和方法重写 的原理吗?
共 19059字,需浏览 39分钟
·
2021-05-20 03:27
阅读本文大概需要 13 分钟。
来自:blog.csdn.net/zwx900102/article/details/108027295
前言
思考
栈帧
局部变量表(Local Variables)
操作数栈(Operand Stacks)
动态连接(Dynamic Linking)
方法返回地址
正常退出(Normal Method Invocation Completion)
异常终止(Abrupt Method Invocation Completion)
其他附加信息
方法调用流程演示
package com.zwx.jvm;
public class JVMDemo {
public static void main(String[] args) {
int sum = add(1, 2);
print(sum);
}
public static int add(int a, int b) {
a = 3;
int result = a + b;
return result;
}
public static void print(int num) {
System.out.println(num);
}
}
javap -c xxx\xxx\JVMDemo.class >1.txt
Compiled from "JVMDemo.java"
public class com.zwx.jvm.JVMDemo {
public com.zwx.jvm.JVMDemo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_1
1: iconst_2
2: invokestatic #2 // Method add:(II)I
5: istore_1
6: iload_1
7: invokestatic #3 // Method print:(I)V
10: return
public static int add(int, int);
Code:
0: iconst_3
1: istore_0
2: iload_0
3: iload_1
4: iadd
5: istore_2
6: iload_2
7: ireturn
public static void print(int);
Code:
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: iload_0
4: invokevirtual #5 // Method java/io/PrintStream.println:(I)V
7: return
}
iconst_i
表示将整型数字i压入操作数栈,注意,这里i的返回只有-1~5,如果不在这个范围会采用其他指令,如当int取值范围是[-128,127]时,会采用bipush指令。invokestatic
表示调用一个静态方法istore_n
这里表示将一个整型数字存入局部变量表的索引n位置,因为局部变量表是通过一个数组形式来存储变量的iload_n
表示将局部变量位置n的变量压入操作数栈ireturn
将当前方法的结果返回到上一个栈帧invokevirtual
调用虚方法
1、代码编译之后大致得到如下的一个Java虚拟机栈,注意这时候操作数栈都是空的(pc寄存器的值在这里暂不考虑 ,实际上调用指令的过程,pc寄存器是会一直发生变化的)
2、执行iconst_1和iconst_2两个指令,也就是从本地变量中把整型1和2两个数字压入操作数栈内:
3、执行invokestatic指令,调用add方法,会再次创建一个新的栈帧入栈,并且会将参数a和b存入add栈帧中的本地变量表
4、add栈帧中调用iconst_3指令,从本地变量中将整型3压入操作数栈
5、add栈帧中调用istore_0,表示将当前的栈顶元素存入局部变量表index[0]的位置,也就是赋值给a。
6、调用iload_0和iload_1,将局部变量表中index[0]和index[1]两个位置的变量压入操作数栈
7、最后执行iadd指令:将3和2弹出栈后将两个数相加,得到5,并将得到的结果5重新压入栈内
8、执行istore_2指令,将当前栈顶元素弹出存入局部变量表index[2]的位置,并再次调用iload_2从局部变量表内将index[2]位置的数据压入操作数栈内9、最后执行ireturn命令将结果5返回main栈帧,此时栈帧add被销毁,回到main栈帧继续后续执行
方法的调用大致就是不断的入栈和出栈的过程,上述的过程省略了很多细节,只关注了大致流程即可,实际调用比图中要复杂的多。
方法调用分析
方法调用指令
1、invokestatic:调用静态方法
2、invokespecial:调用实例构造器方法,私有方法,父类方法
3、invokevirtual:调用所有的虚方法
4、invokeinterface:调用接口方法(运行时会确定一个实现了接口的对象)
方法解析
非虚方法
1、静态方法
2、私有方法
3、实例构造器方法
4、父类方法(通过super.xxx调用,因为Java是单继承,只有一个父类,所以可以确定方法的唯一)
方法重载
package com.zwx.jvm.overload;
public class OverloadDemo {
static class Human {
}
static class Man extends Human {
}
static class WoMan extends Human {
}
public void hello(Human human) {
System.out.println("Hi,Human");
}
public void hello(Man man) {
System.out.println("Hi,Man");
}
public void hello(WoMan woMan) {
System.out.println("Hi,Women");
}
public static void main(String[] args) {
OverloadDemo overloadDemo = new OverloadDemo();
Human man = new Man();
Human woman = new WoMan();
overloadDemo.hello(man);
overloadDemo.hello(woman);
}
}
Hi,Human
Hi,Human
宗量
单分派:根据1个宗量对方法进行选择
多分派:根据1个以上的宗量对方法进行选择
overloadDemo.hello(man);
Human man = new Man();
静态分派
package com.zwx.jvm.overload;
import java.io.Serializable;
public class OverloadDemo2 {
public static void hello(Object a){
System.out.println("Hello,Object");
}
public static void hello(double a){
System.out.println("Hello,double");
}
public static void hello(Double a){
System.out.println("Hello,Double");
}
public static void hello(float a){
System.out.println("Hello,float");
}
public static void hello(long a){
System.out.println("Hello,long");
}
public static void hello(int a){
System.out.println("Hello,int");
}
public static void hello(Character a){
System.out.println("Hello,Character");
}
public static void hello(char a){
System.out.println("Hello,char");
}
public static void hello(char ...a){
System.out.println("Hello,chars");
}
public static void hello(Serializable a){
System.out.println("Hello,Serializable");
}
public static void main(String[] args) {
OverloadDemo2.hello('1');
}
}
Hello,char
Hello,int
char->int->long->float->double->Character->Serializable->Object->chars
方法重写
package com.zwx.jvm.override;
public class OverrideDemo {
static class Human {
public void hello(Human human) {
System.out.println("Hi,Human");
}
}
static class Man extends Human {
@Override
public void hello(Human human) {
System.out.println("Hi,Man");
}
}
static class WoMan extends Human {
@Override
public void hello(Human human) {
System.out.println("Hi,Women");
}
}
public static void main(String[] args) {
Human man = new Man();
Human woman = new WoMan();
man.hello(man);
man.hello(woman);
woman.hello(woman);
woman.hello(man);
}
}
Hi,Man
Hi,Man
Hi,Women
Hi,Women
Compiled from "OverrideDemo.java"
public class com.zwx.jvm.override.OverrideDemo {
public com.zwx.jvm.override.OverrideDemo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class com/zwx/jvm/override/OverrideDemo$Man
3: dup
4: invokespecial #3 // Method com/zwx/jvm/override/OverrideDemo$Man."<init>":()V
7: astore_1
8: new #4 // class com/zwx/jvm/override/OverrideDemo$WoMan
11: dup
12: invokespecial #5 // Method com/zwx/jvm/override/OverrideDemo$WoMan."<init>":()V
15: astore_2
16: aload_1
17: aload_1
18: invokevirtual #6 // Method com/zwx/jvm/override/OverrideDemo$Human.hello:(Lcom/zwx/jvm/override/OverrideDemo$Human;)V
21: aload_1
22: aload_2
23: invokevirtual #6 // Method com/zwx/jvm/override/OverrideDemo$Human.hello:(Lcom/zwx/jvm/override/OverrideDemo$Human;)V
26: aload_2
27: aload_2
28: invokevirtual #6 // Method com/zwx/jvm/override/OverrideDemo$Human.hello:(Lcom/zwx/jvm/override/OverrideDemo$Human;)V
31: aload_2
32: aload_1
33: invokevirtual #6 // Method com/zwx/jvm/override/OverrideDemo$Human.hello:(Lcom/zwx/jvm/override/OverrideDemo$Human;)V
36: return
}
main方法大概解释一下,main方法中,第7行(Code列序号)和第15行是分别把Man对象实例和Women对象实例存入局部变量变的index[1]和index[2]两个位置,然后16,17两行,21,22两行,26,27两行,31,32两行分别是把需要用到的方法调用者和参数压入操作数栈,然后调用invokevirtual指令调用方法。
1、找到当前操作数栈中的方法接收者(调用者),记下来,比如叫Caller
2、然后在类型Caller中去找方法,如果找到方法签名一致的方法,则停止搜索,开始对方法校验,校验通过直接调用,校验不通过,直接抛IllegalAccessError异常
3、如果在Caller中没有找到方法签名一致的方法,则往上找父类,以此类推,直到找到为止,如果到顶了还没找到匹配的方法,则抛出AbstractMethodError异常
动态分派
单分派与多分派
1、静态类型
2、方法参数
总结
推荐阅读:
【195期】MySQL中的条件判断函数 CASE WHEN、IF、IFNULL你会用吗?
微信扫描二维码,关注我的公众号
朕已阅