【198期】面試官:你能說出 方法重載和方法重寫 的原理嗎?
閱讀本文大概需要 13 分鐘。
來自:blog.csdn.net/zwx900102/article/details/108027295
前言
思考
棧幀
局部變量表(Local Variables)
操作數(shù)棧(Operand Stacks)
動(dòng)態(tài)連接(Dynamic Linking)
方法返回地址
正常退出(Normal Method Invocation Completion)
異常終止(Abrupt Method Invocation Completion)
其他附加信息
方法調(diào)用流程演示
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
表示將整型數(shù)字i壓入操作數(shù)棧,注意,這里i的返回只有-1~5,如果不在這個(gè)范圍會(huì)采用其他指令,如當(dāng)int取值范圍是[-128,127]時(shí),會(huì)采用bipush指令。invokestatic
表示調(diào)用一個(gè)靜態(tài)方法istore_n
這里表示將一個(gè)整型數(shù)字存入局部變量表的索引n位置,因?yàn)榫植孔兞勘硎峭ㄟ^一個(gè)數(shù)組形式來存儲(chǔ)變量的iload_n
表示將局部變量位置n的變量壓入操作數(shù)棧ireturn
將當(dāng)前方法的結(jié)果返回到上一個(gè)棧幀invokevirtual
調(diào)用虛方法
1、代碼編譯之后大致得到如下的一個(gè)Java虛擬機(jī)棧,注意這時(shí)候操作數(shù)棧都是空的(pc寄存器的值在這里暫不考慮 ,實(shí)際上調(diào)用指令的過程,pc寄存器是會(huì)一直發(fā)生變化的)

2、執(zhí)行iconst_1和iconst_2兩個(gè)指令,也就是從本地變量中把整型1和2兩個(gè)數(shù)字壓入操作數(shù)棧內(nèi):

3、執(zhí)行invokestatic指令,調(diào)用add方法,會(huì)再次創(chuàng)建一個(gè)新的棧幀入棧,并且會(huì)將參數(shù)a和b存入add棧幀中的本地變量表

4、add棧幀中調(diào)用iconst_3指令,從本地變量中將整型3壓入操作數(shù)棧

5、add棧幀中調(diào)用istore_0,表示將當(dāng)前的棧頂元素存入局部變量表index[0]的位置,也就是賦值給a。

6、調(diào)用iload_0和iload_1,將局部變量表中index[0]和index[1]兩個(gè)位置的變量壓入操作數(shù)棧

7、最后執(zhí)行iadd指令:將3和2彈出棧后將兩個(gè)數(shù)相加,得到5,并將得到的結(jié)果5重新壓入棧內(nèi)

8、執(zhí)行istore_2指令,將當(dāng)前棧頂元素彈出存入局部變量表index[2]的位置,并再次調(diào)用iload_2從局部變量表內(nèi)將index[2]位置的數(shù)據(jù)壓入操作數(shù)棧內(nèi)
9、最后執(zhí)行ireturn命令將結(jié)果5返回main棧幀,此時(shí)棧幀add被銷毀,回到main棧幀繼續(xù)后續(xù)執(zhí)行

方法的調(diào)用大致就是不斷的入棧和出棧的過程,上述的過程省略了很多細(xì)節(jié),只關(guān)注了大致流程即可,實(shí)際調(diào)用比圖中要復(fù)雜的多。
方法調(diào)用分析
方法調(diào)用指令
1、invokestatic:調(diào)用靜態(tài)方法
2、invokespecial:調(diào)用實(shí)例構(gòu)造器方法,私有方法,父類方法
3、invokevirtual:調(diào)用所有的虛方法
4、invokeinterface:調(diào)用接口方法(運(yùn)行時(shí)會(huì)確定一個(gè)實(shí)現(xiàn)了接口的對(duì)象)
方法解析
非虛方法
1、靜態(tài)方法
2、私有方法
3、實(shí)例構(gòu)造器方法
4、父類方法(通過super.xxx調(diào)用,因?yàn)镴ava是單繼承,只有一個(gè)父類,所以可以確定方法的唯一)
方法重載
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
宗量
單分派:根據(jù)1個(gè)宗量對(duì)方法進(jìn)行選擇
多分派:根據(jù)1個(gè)以上的宗量對(duì)方法進(jìn)行選擇
overloadDemo.hello(man);
Human man = new Man();
靜態(tài)分派
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列序號(hào))和第15行是分別把Man對(duì)象實(shí)例和Women對(duì)象實(shí)例存入局部變量變的index[1]和index[2]兩個(gè)位置,然后16,17兩行,21,22兩行,26,27兩行,31,32兩行分別是把需要用到的方法調(diào)用者和參數(shù)壓入操作數(shù)棧,然后調(diào)用invokevirtual指令調(diào)用方法。
1、找到當(dāng)前操作數(shù)棧中的方法接收者(調(diào)用者),記下來,比如叫Caller
2、然后在類型Caller中去找方法,如果找到方法簽名一致的方法,則停止搜索,開始對(duì)方法校驗(yàn),校驗(yàn)通過直接調(diào)用,校驗(yàn)不通過,直接拋IllegalAccessError異常
3、如果在Caller中沒有找到方法簽名一致的方法,則往上找父類,以此類推,直到找到為止,如果到頂了還沒找到匹配的方法,則拋出AbstractMethodError異常
動(dòng)態(tài)分派
單分派與多分派
1、靜態(tài)類型
2、方法參數(shù)
總結(jié)
推薦閱讀:
【197期】華為OD兩輪技術(shù)面試記錄,給后來人一個(gè)參考!
【196期】夯實(shí)基礎(chǔ),Java8新特性Stream詳細(xì)教程
【195期】MySQL中的條件判斷函數(shù) CASE WHEN、IF、IFNULL你會(huì)用嗎?
微信掃描二維碼,關(guān)注我的公眾號(hào)
朕已閱 

