<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【198期】面試官:你能說出 方法重載和方法重寫 的原理嗎?

          共 19059字,需瀏覽 39分鐘

           ·

          2021-05-20 03:27

          程序員的成長之路
          互聯(lián)網(wǎng)/程序員/技術(shù)/資料共享 
          關(guān)注


          閱讀本文大概需要 13 分鐘。

          來自:blog.csdn.net/zwx900102/article/details/108027295

          前言

          JVM執(zhí)行字節(jié)碼指令是基于棧的架構(gòu),就是說所有的操作數(shù)都必須先入棧,然后再根據(jù)需要出棧進(jìn)行操作計(jì)算,再把結(jié)果進(jìn)行入棧,這個(gè)流程和基于寄存器的架構(gòu)是有本質(zhì)區(qū)別的,而基于寄存器架構(gòu)來實(shí)現(xiàn),在不同的機(jī)器上可能會(huì)無法做到完全兼容,這也是Java會(huì)選擇基于棧的設(shè)計(jì)的原因之一。

          思考

          我們思考下,當(dāng)我們調(diào)用一個(gè)方法時(shí),參數(shù)是怎么傳遞的,返回值又是怎么保存的,一個(gè)方法調(diào)用之后又是如何繼續(xù)下一個(gè)方法調(diào)用的呢?調(diào)用過程中肯定會(huì)存儲(chǔ)一些方法的參數(shù)和返回值等信息,這些信息存儲(chǔ)在哪里呢?
          我們知道,每次調(diào)用一個(gè)方法就會(huì)產(chǎn)生一個(gè)棧幀,所以我們肯定可以想到棧幀就存儲(chǔ)了所有調(diào)用過程中需要使用到的數(shù)據(jù)。現(xiàn)在就讓我們深入的去了解一下Java虛擬機(jī)棧中的棧幀吧。

          棧幀

          當(dāng)我們調(diào)用一個(gè)方法的時(shí)候,就會(huì)產(chǎn)生一個(gè)棧幀,當(dāng)一個(gè)方法調(diào)用完成時(shí),它所對(duì)應(yīng)的棧幀將被銷毀,無論這種完成是正常的還是突然的(拋出一個(gè)未捕獲的異常)。
          每個(gè)棧幀中包括局部變量表(Local Variables)、操作數(shù)棧(Operand Stack)、動(dòng)態(tài)鏈接(Dynamic Linking)、方法返回地址(Return Address)和額外的附加信息。
          在給定的線程當(dāng)中,永遠(yuǎn)只有一個(gè)棧幀是活動(dòng)的,所以活動(dòng)的棧幀又稱之為當(dāng)前棧幀,而其對(duì)應(yīng)的方法則稱之為當(dāng)前方法,定義了當(dāng)前方法的類則稱之為當(dāng)前類。當(dāng)一個(gè)方法調(diào)用結(jié)束時(shí),其對(duì)應(yīng)的棧幀也會(huì)被丟棄。

          局部變量表(Local Variables)

          局部變量表是以數(shù)組的形式存儲(chǔ)的,而且當(dāng)前棧幀的方法所需要分配的最大長度是在編譯時(shí)就確定了。局部變量表通過index來尋址,變量從index[0]開始傳遞。
          局部變量表的數(shù)組中,每一個(gè)位置可以保存一個(gè)32位的數(shù)據(jù)類型:boolean、byte、char、short、int、float、reference或returnAddress類型的值。而對(duì)于64位的數(shù)據(jù)類型long和double則需要兩個(gè)位置來存儲(chǔ),但是因?yàn)榫植孔兞勘硎菍儆诰€程私有的,所以雖然被分割為2個(gè)變量存儲(chǔ),依然不用擔(dān)心會(huì)出現(xiàn)安全性問題。
          對(duì)于64位的數(shù)據(jù)類型,假如其占用了數(shù)組中的index[n]和index[n+1]兩個(gè)位置,那么不允許單獨(dú)訪問其中的某一個(gè)位置,Java虛擬機(jī)規(guī)范中規(guī)定,如果出現(xiàn)一個(gè)64位的數(shù)據(jù)被單獨(dú)訪問某一部分時(shí),則在類加載機(jī)制中的校驗(yàn)階段就應(yīng)該拋出異常。
          Java虛擬機(jī)在方法調(diào)用時(shí)使用局部變量進(jìn)行傳遞參數(shù)。在類方法(static方法)調(diào)用中,所有參數(shù)都以從局部變量中的index[0]開始進(jìn)行參數(shù)傳遞。而在實(shí)例方法調(diào)用上,index[0]固定用來傳遞方法所屬于的對(duì)象實(shí)例,其余所有參數(shù)則在從局部變量表內(nèi)index[1]的位置開始進(jìn)行傳遞。
          注意:局部變量表中的變量不可以直接使用,如需要使用的話,必須通過相關(guān)指令將其加載至操作數(shù)棧中作為操作數(shù)才能使用

          操作數(shù)棧(Operand Stacks)

          操作數(shù)棧,在上下文語義清晰時(shí),也可以稱之為操作棧(Operand Stack),是一個(gè)后進(jìn)先出(Last In First Out,LIFO)棧,同局部變量表一樣,操作數(shù)棧的最大深度也是在編譯時(shí)就確定的。
          操作數(shù)棧在剛被創(chuàng)建時(shí)(也就是方法剛被執(zhí)行的時(shí)候)是空的,然后在執(zhí)行方法的過程中,通過虛擬機(jī)指令將常量/值從局部變量表或字段加載到操作數(shù)棧中,然后對(duì)其進(jìn)行操作,并將操作結(jié)果壓入棧內(nèi)。
          操作數(shù)堆棧上的每個(gè)條目都可以保存任何Java虛擬機(jī)類型的值,包括long或double類型的值。
          注意:我們必須以適合其類型的方式對(duì)操作數(shù)堆棧中的值進(jìn)行操作。例如,不可能將兩個(gè)int類型的值壓入棧后將其視為long類型,也不可能將兩個(gè)float類型值壓入棧內(nèi)后使用iadd指令將其添加。

          動(dòng)態(tài)連接(Dynamic Linking)

          每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池中該棧幀所屬方法的引用,持有這個(gè)引用是為了支持方法調(diào)用過程中的動(dòng)態(tài)連接。
          在Class文件中的常量池中存有大量的符號(hào)引用,字節(jié)碼中的方法調(diào)用指令就以常量池中指向方法的符號(hào)引用作為參數(shù),這些符號(hào)引用一部分會(huì)在類加載階段或者第一次使用的時(shí)候就轉(zhuǎn)化為直接引用,這種就稱為靜態(tài)解析。而另外一部分則會(huì)在每一次運(yùn)行期間才會(huì)轉(zhuǎn)化為直接引用,這部分就稱為動(dòng)態(tài)連接。

          方法返回地址

          當(dāng)一個(gè)方法開始執(zhí)行后,只有兩種方式可以退出:一種是遇到方法返回的字節(jié)碼指令;一種是遇見異常,并且這個(gè)異常沒有在方法體內(nèi)得到處理。

          正常退出(Normal Method Invocation Completion)

          如果對(duì)當(dāng)前方法的調(diào)用正常完成,則可能會(huì)向調(diào)用方法返回一個(gè)值。當(dāng)被調(diào)用的方法執(zhí)行其中一個(gè)返回指令時(shí),返回指令的選擇必須與被返回值的類型相匹配(如果有的話)。
          方法正常退出時(shí),當(dāng)前棧幀通過將調(diào)用者的pc程序計(jì)數(shù)器適當(dāng)?shù)牟⑻^當(dāng)前的調(diào)用指令來恢復(fù)調(diào)用程序的狀態(tài),包括它的局部變量表和操作數(shù)堆棧。然后繼續(xù)在調(diào)用方法的棧幀來執(zhí)行后續(xù)流程,如果有返回值的話則需要將返回值壓入操作數(shù)棧。

          異常終止(Abrupt Method Invocation Completion)

          如果在方法中執(zhí)行Java虛擬機(jī)指令導(dǎo)致Java虛擬機(jī)拋出異常,并且該異常沒有在方法中處理,那么方法調(diào)用會(huì)突然結(jié)束,因?yàn)楫惓?dǎo)致的方法突然結(jié)束永遠(yuǎn)不會(huì)有返回值返回給它的調(diào)用者。

          其他附加信息

          這一部分具體要看虛擬機(jī)產(chǎn)商是如何實(shí)現(xiàn)的,虛擬機(jī)規(guī)范并沒有對(duì)這部分進(jìn)行描述。

          方法調(diào)用流程演示

          上面的概念聽起來有點(diǎn)抽象,下面我們就通過一個(gè)簡單的例子來演示一下方法的執(zhí)行流程。

          package com.zwx.jvm;

          public class JVMDemo {
              public static void main(String[] args) {
                  int sum = add(12);
                  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);
              }
          }

          要想了解Java虛擬機(jī)的執(zhí)行流程,那么我們必須要對(duì)類進(jìn)行編譯,得到字節(jié)碼文件,執(zhí)行如下命令:

          javap -c xxx\xxx\JVMDemo.class >1.txt

          將JVMDemo.class生成的字節(jié)碼指令輸出到1.txt文件中,然后打開,看到如下字節(jié)碼指令:

          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
          }

          如果是第一次接觸可能指令看不太懂,但是大致的類結(jié)構(gòu)還是很清晰的,我們先來對(duì)用到的字節(jié)碼指令大致說明一下:
          • 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)用虛方法

          了解了字節(jié)碼指令的大概意思,接下來就讓我們來演示一下主要的幾個(gè)執(zhí)行流程:
          • 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)用分析

          我們知道,Java是一種面向?qū)ο笳Z言,支持多態(tài),而多態(tài)的體現(xiàn)形式就是方法重載和方法重寫,那么Java虛擬機(jī)又是如何確認(rèn)我們應(yīng)該調(diào)用哪一個(gè)方法的呢?

          方法調(diào)用指令

          首先,我們來看一下方法的字節(jié)碼調(diào)用指令,在Java中,提供了4種字節(jié)碼指令來調(diào)用方法(jdk1.7之前):
          • 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ì)象)

          注意:在JDK1.7開始,Java新增了一個(gè)指令invokedynamic,這個(gè)是為了實(shí)現(xiàn)“動(dòng)態(tài)類型語言”而引入的,在這里我們暫不討論

          方法解析

          在類加載機(jī)制中的解析階段,主要做的事情就是將符號(hào)引用轉(zhuǎn)為直接引用,但是,對(duì)方法的調(diào)用而言,有一個(gè)前提,那就是在方法真正運(yùn)行之前就可以唯一確定具體要調(diào)用哪一個(gè)方法,而且這個(gè)方法在運(yùn)行期間是不可變的。只有滿足這個(gè)前提的方法才會(huì)在解析階段直接被替換為直接引用,否則只能等到運(yùn)行時(shí)才能最終確定。

          非虛方法

          在Java語言中,滿足“編譯器可知,運(yùn)行期不可變”這個(gè)前提的方法,被稱之為非虛方法。非虛方法在類加載機(jī)制中的解析階段就可以直接將符號(hào)引用轉(zhuǎn)化為直接引用。非虛方法有4種:
          • 1、靜態(tài)方法

          • 2、私有方法

          • 3、實(shí)例構(gòu)造器方法

          • 4、父類方法(通過super.xxx調(diào)用,因?yàn)镴ava是單繼承,只有一個(gè)父類,所以可以確定方法的唯一)

          除了非虛方法之外的非final方法就被稱之為虛方法,虛方法需要運(yùn)行時(shí)才能確定真正調(diào)用哪一個(gè)方法。Java語言規(guī)范中明確指出,final方法是一種非虛方法,但是final又屬于比較特殊的存在,因?yàn)閒inal方法和其他非虛方法調(diào)用的字節(jié)碼指令不一樣。
          知道了虛方法的類型,再結(jié)合上面的方法的調(diào)用指令,我們可以知道,虛方法就是通過字節(jié)碼指令invokestatic和invokespecial調(diào)用的,而final方法又是一個(gè)例外,final方法是通過字節(jié)碼指令invokevirtual調(diào)用的,但是因?yàn)閒inal方法的特性就是不可被重寫,無法覆蓋,所以必然是唯一的,雖然調(diào)用指令不同,但是依然屬于非虛方法的范疇。

          方法重載

          先來看一個(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);
              }
          }

          輸出結(jié)果為:

          Hi,Human
          Hi,Human

          這里,Java虛擬機(jī)為什么會(huì)選擇參數(shù)為Human的方法來進(jìn)行調(diào)用呢?
          在解釋這個(gè)問題之前,我們先來介紹一個(gè)概念:宗量

          宗量

          方法的接收者(調(diào)用者)和方法參數(shù)統(tǒng)稱為宗量。而最終決定方法的分派就是基于宗量來選擇的,故而根據(jù)基于多少種宗量來選擇方法又可以分為:
          • 單分派:根據(jù)1個(gè)宗量對(duì)方法進(jìn)行選擇

          • 多分派:根據(jù)1個(gè)以上的宗量對(duì)方法進(jìn)行選擇

          知道了方法的分派是基于宗量來進(jìn)行的,那我們?cè)倩氐缴厦娴睦又芯秃芎美斫饬恕?/section>

          overloadDemo.hello(man);

          這句代碼中overloadDemo表示接收者,man表示參數(shù),而接收者是確定唯一的,就是overloadDemo實(shí)例,所以決定調(diào)用哪個(gè)方法的只有參數(shù)(包括參數(shù)類型和個(gè)數(shù)和順序)這一個(gè)宗量。我們?cè)倏纯磪?shù)類型:

          Human man = new Man();

          這句話中,Human稱之為變量的靜態(tài)類型,而Man則稱之為變量的實(shí)際類型,而Java虛擬機(jī)在確認(rèn)重載方法時(shí)是基于參數(shù)的靜態(tài)類型來作為判斷依據(jù)的,故而最終實(shí)際上不管你右邊new的對(duì)象是哪個(gè),調(diào)用的都是參數(shù)類型為Human的方法。

          靜態(tài)分派

          所有依賴變量的靜態(tài)類型來定位方法執(zhí)行的分派動(dòng)作就稱之為靜態(tài)分派。靜態(tài)分派最典型的應(yīng)用就是方法重載。
          方法重載在編譯期就能確定方法的唯一,不過雖然如此,但是在有些情況下,這個(gè)重載版本不是唯一的,甚至是有點(diǎn)模糊的。產(chǎn)生這個(gè)原因就是因?yàn)樽置媪坎⒉恍枰x,所以字面量就沒有今天類型,比如我們直接調(diào)用一個(gè)方法:xxx.xxx(‘1’),這個(gè)字面量1就是模糊的,并沒有對(duì)應(yīng)靜態(tài)類型。我們?cè)賮砜匆粋€(gè)例子:

          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');
              }
          }

          這里的輸出結(jié)果是

          Hello,char

          然后如果把該方法注釋掉,就會(huì)輸出:

          Hello,int

          再把int方法注釋掉,那么會(huì)依次按照如下順序進(jìn)行方法調(diào)用輸出:

          char->int->long->float->double->Character->Serializable->Object->chars

          可以看到,多參數(shù)的優(yōu)先級(jí)最低,之所以會(huì)輸出Serializable是因?yàn)榘b類Character實(shí)現(xiàn)了Serializable接口,注意示例中double的包裝類Double,并不會(huì)被執(zhí)行。

          方法重寫

          我們把上面第1個(gè)例子修改一下:

          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);
              }
          }

          輸出結(jié)果為:

          Hi,Man
          Hi,Man
          Hi,Women
          Hi,Women

          這里靜態(tài)類型都是Human,但是卻輸出了兩種結(jié)果,所以肯定不是按照靜態(tài)類型來分派方法了,而從結(jié)果來看應(yīng)該是按照了調(diào)用者的實(shí)際類型來進(jìn)行的判斷。
          執(zhí)行javap命令把類轉(zhuǎn)換成字節(jié)碼:

          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
          }

          我們可以發(fā)現(xiàn)這里的方法調(diào)用使用了指令invokevirtual來調(diào)用,因?yàn)楦鶕?jù)上面的分類可以判斷,hello方法均是虛方法。
          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)用方法。
          所以上面最關(guān)鍵的就是invokevirtual指令到底是如何工作的呢?invokevirtual主要是按照如下步驟進(jìn)行方法選擇的:
          • 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)分派

          上面的方法重寫例子中,在運(yùn)行期間才能根據(jù)實(shí)際類型來確定方法的執(zhí)行版本的分派過程就稱之為動(dòng)態(tài)分派。

          單分派與多分派

          上面方法重載的第1個(gè)示例中,是一個(gè)靜態(tài)分派過程,靜態(tài)分配過程中Java虛擬機(jī)選擇目標(biāo)方法有兩點(diǎn):
          • 1、靜態(tài)類型

          • 2、方法參數(shù)

          也就是用到了2個(gè)宗量來進(jìn)行分派,所以是一個(gè)靜態(tài)多分派的過程。
          而上面方法重寫的例子中,因?yàn)榉椒ê灻枪潭ǖ模簿褪菂?shù)是固定的,那么就只有一個(gè)宗量-靜態(tài)類型,能最終確定方法的調(diào)用,所以屬于動(dòng)態(tài)單分派。
          所以可以得出對(duì)Java而言:Java是一門靜態(tài)多分派,動(dòng)態(tài)單分派語言

          總結(jié)

          本文主要介紹了一下Java虛擬機(jī)中,方法的執(zhí)行流程以及方法執(zhí)行過程中時(shí),Java虛擬機(jī)棧中的內(nèi)存布局,并從字節(jié)碼的角度詮釋了Java虛擬機(jī)是如何針對(duì)方法重載和方法重寫來做出最終調(diào)用方法的選擇的。
          <END>

          推薦閱讀:

          【197期】華為OD兩輪技術(shù)面試記錄,給后來人一個(gè)參考!

          【196期】夯實(shí)基礎(chǔ),Java8新特性Stream詳細(xì)教程

          【195期】MySQL中的條件判斷函數(shù) CASE WHEN、IF、IFNULL你會(huì)用嗎?

          5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機(jī),樹莓派,等等。在公眾號(hào)內(nèi)回復(fù)「2048」,即可免費(fèi)獲取!!

          微信掃描二維碼,關(guān)注我的公眾號(hào)

          朕已閱 

          瀏覽 46
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黄色片网站国产干 | 成人毛片在线乱码 | 男操女逼射逼心国产中字传媒视频 | 国产精品久久久久久精 | 黄色照片视频 |