<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>

          三年前,我做錯(cuò)的那道面試題

          共 4897字,需瀏覽 10分鐘

           ·

          2020-09-04 13:41

          三年前,我做了一道關(guān)于try-catch-finnaly的面試題,但我做錯(cuò)了,當(dāng)時(shí)面試官問(wèn)我為啥錯(cuò)了,我告訴他,我平常不會(huì)寫這么傻逼的代碼,然后面試官就沒(méi)有問(wèn)我了。。。。

          最近看到其他面試的童鞋,又讓我想起了這道題,剛好也試著分析下。

          我們知道Java虛擬機(jī)棧是線程私有的,它的生命周期與線程相同

          虛擬機(jī)棧是Java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)一部分,它描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。

          每一個(gè)方法從調(diào)用直至執(zhí)行完成的過(guò)程就對(duì)應(yīng)著一個(gè)幀棧在虛擬機(jī)中入棧到出棧的過(guò)程。

          加載和存儲(chǔ)指令用于將數(shù)據(jù)在幀棧中的局部變量和操作數(shù)棧之前來(lái)回傳輸,這類指令包括如下內(nèi)容:

          1. 將一個(gè)局部變量加載到操作棧:iload, iload_、aload、aload_

          2. 將一個(gè)數(shù)值從操作數(shù)棧存儲(chǔ)到局部變量表:istore、 istore_、astore、astore_

          3. 將一個(gè)常量加載到操作數(shù)棧:ldc、iconst_

          來(lái)看看當(dāng)初我做錯(cuò)的那道題

          public?class?Test?{

          ??public?static?void?main(String[]?args)?{

          ??????System.out.println(getNumber());
          ??}

          ??public?static?int?getNumber()?{
          ????int?x;
          ????try?{
          ??????x=1;
          ??????return?x;
          ????}?catch?(Exception?e)?{
          ??????x=2;
          ??????return?x;
          ????}
          ????finally{
          ??????x=3;
          ????}

          ??}

          }

          上面的代碼輸出結(jié)果是1,我們首先來(lái)看看它的字節(jié)碼是怎樣的?

          ?public?static?int?getNumber();
          ??Code:
          ???0:?iconst_1
          ???1:?istore_0
          ???2:?iload_0
          ???3:?istore_1
          ???4:?iconst_3
          ???5:?istore_0
          ???6:?iload_1
          ???7:?ireturn
          ???8:?astore_1
          ???9:?iconst_2
          ??10:?istore_0
          ??11:?iload_0
          ??12:?istore_2
          ??13:?iconst_3
          ??14:?istore_0
          ??15:?iload_2
          ??16:?ireturn
          ??17:?astore_3
          ??18:?iconst_3
          ??19:?istore_0
          ??20:?aload_3
          ??21:?athrow
          Exception?table:
          ???from????to??target?type
          ???????0?????4?????8???Class?java/lang/Exception
          ???????0?????4????17???any
          ???????8????13????17???any
          ??}

          我來(lái)根據(jù)字節(jié)碼來(lái)分析下代碼是如何運(yùn)行的:

          操作數(shù)棧: 先進(jìn)后出的一個(gè)數(shù)據(jù)結(jié)構(gòu)

          局部變量表: 可以認(rèn)為是一個(gè)數(shù)組,下標(biāo)從0開始

          以下是執(zhí)行每一條指令的時(shí)候操作數(shù)棧和局部變量表的變化情況:

          iconst_1(將int類型數(shù)字1放入操作數(shù)棧頂):
          操作數(shù)棧:?1
          局部變量表:

          istore_0(將操作數(shù)棧頂int型數(shù)字出棧存入變量表第1個(gè)本地變量):
          操作數(shù)棧:?
          局部變量表:1

          iload_0(將變量表第1個(gè)int型本地變量推送至棧頂):
          操作數(shù)棧:?1
          局部變量表:

          istore_1(將操作數(shù)棧頂int型數(shù)字出棧存入變量表第2個(gè)本地變量):
          操作數(shù)棧:
          局部變量表:?null?1

          iconst_3(將int類型數(shù)字3放入操作數(shù)棧頂):
          操作數(shù)棧:?3
          局部變量表:?null?1

          istore_0(將操作數(shù)棧頂int型數(shù)字出棧存入變量表第1個(gè)本地變量):
          操作數(shù)棧:?
          局部變量表:?3?1

          iload1(將變量表第2個(gè)int型本地變量推送至棧頂):
          操作數(shù)棧:?1
          局部變量表:?3

          ireturn(從棧頂返回int型數(shù)字,方法結(jié)束):
          返回1

          可以看到ireturn后面的代碼就不會(huì)被執(zhí)行了,我們也就不進(jìn)行翻譯了。實(shí)際上try-catch-finally字節(jié)碼塊中是沒(méi)有finally的, 根據(jù)字節(jié)碼我們可以將代碼簡(jiǎn)化成這樣,記住這段代碼,try-finally翻譯過(guò)來(lái)就是這樣的

          public?static?int?getNumber()?{
          ??int?x;
          ??int?returnValue;
          ??try?{
          ????x=1;
          ????returnValue?=?x;
          ????x?=?3;
          ????return?returnValue;
          ??}?catch?(Exception?e)?{
          ????x=2;
          ????return?x;
          ??}
          }

          那如果將代碼變成這樣呢?

          public?static?int?getNumber()?{
          ??int?x;
          ??try?{
          ????x=1;
          ????return?x;
          ??}?catch?(Exception?e)?{
          ????x=2;
          ????return?x;
          ??}
          ??finally{
          ????x=3;
          ????return?x;
          ??}
          }

          我相信你能知道輸出的結(jié)果是3,我們同樣來(lái)看下字節(jié)碼是怎樣的?

          public?static?int?getNumber();
          ??Code:
          ?????0:?iconst_1
          ?????1:?istore_0
          ?????2:?iload_0
          ?????3:?istore_1
          ?????4:?iconst_3
          ?????5:?istore_0
          ?????6:?iload_0
          ?????7:?ireturn
          ?????8:?astore_1
          ?????9:?iconst_2
          ????10:?istore_0
          ????11:?iload_0
          ????12:?istore_2
          ????13:?iconst_3
          ????14:?istore_0
          ????15:?iload_0
          ????16:?ireturn
          ????17:?astore_3
          ????18:?iconst_3
          ????19:?istore_0
          ????20:?iload_0
          ????21:?ireturn
          ??Exception?table:
          ?????from????to??target?type
          ?????????0?????4?????8???Class?java/lang/Exception
          ?????????0?????4????17???any
          ?????????8????13????17???any
          }

          同樣的我們只分析執(zhí)行到的部分

          iconst_1(將int類型數(shù)字1放入操作數(shù)棧頂):
          操作數(shù)棧:?1
          局部變量表:

          istore_0(將操作數(shù)棧頂int型數(shù)字出棧存入變量表第1個(gè)本地變量):
          操作數(shù)棧:?
          局部變量表:1

          iload_0(將變量表第1個(gè)int型本地變量推送至棧頂):
          操作數(shù)棧:?1
          局部變量表:

          istore_1(將操作數(shù)棧頂int型數(shù)字出棧存入變量表第2個(gè)本地變量):
          操作數(shù)棧:?
          局部變量表:null 1

          iconst_3(將int類型數(shù)字3放入操作數(shù)棧頂):
          操作數(shù)棧:?3
          局部變量表:null 1

          istore_0(將操作數(shù)棧頂int型數(shù)字出棧存入變量表第1個(gè)本地變量):
          操作數(shù)棧:?
          局部變量表:3 1

          #?注意這里和上面字節(jié)碼的不同之處在于上面是加載變量表中的第二個(gè)int類型本地變量
          iload_0(將變量表第1個(gè)int型本地變量推送至棧頂):
          操作數(shù)棧:?3
          局部變量表:1

          ireturn(從棧頂返回int型數(shù)字,方法結(jié)束):
          返回3

          上面都只是分析了try-finally,我們接著分析下try-catch-finally是如何的


          public?static?int?getNumber()?{
          ??int?x;
          ??try?{
          ????x?=?1/0;
          ????return?x;
          ??}?catch?(Exception?e)?{
          ????x?=?2;
          ????return?x;
          ??}?finally?{
          ????x?=?3;
          ????return?x;
          ??}
          }

          之前的代碼很明顯不會(huì)拋出異常,所以就用不到異常表中的內(nèi)容,但是這里肯定是產(chǎn)生異常(1/0),而在java中對(duì)異常的處理在字節(jié)碼層面是使用Exception Table來(lái)完成的。

          public?static?int?getNumber();
          Code:
          ?0:?iconst_1
          ?1:?iconst_0
          ?2:?idiv
          ?3:?istore_0
          ?4:?iload_0
          ?5:?istore_1
          ?6:?iconst_3
          ?7:?istore_0
          ?8:?iload_0
          ?9:?ireturn
          10:?astore_1
          11:?iconst_2
          12:?istore_0
          13:?iload_0
          14:?istore_2
          15:?iconst_3
          16:?istore_0
          17:?iload_0
          18:?ireturn
          19:?astore_3
          20:?iconst_3
          21:?istore_0
          22:?iload_0
          23:?ireturn
          Exception?table:
          ?from????to??target?type
          ?????0?????6????10???Class?java/lang/Exception
          ?????0?????6????19???any
          ????10????15????19???any
          }

          這個(gè)異常表(Exception table)含義是如果當(dāng)字節(jié)碼在第from行到第to行之間(不包含to行)出現(xiàn)了類型為type或者其子類的異常則轉(zhuǎn)到第target行繼續(xù)處理。當(dāng)type的值為any時(shí),代表任意異常情況都需要轉(zhuǎn)向到target處進(jìn)行處理。

          異常表也相當(dāng)于指明了代碼有可能的分支執(zhí)行情況。老規(guī)矩,我們一行一行來(lái)分析字節(jié)碼

          iconst_1(將int類型數(shù)字1放入操作數(shù)棧頂):
          操作數(shù)棧:?1
          局部變量表:

          iconst_0(將int類型數(shù)字0放入操作數(shù)棧頂):
          操作數(shù)棧:?0?1
          局部變量表:

          idiv(將操作數(shù)棧頂兩int型數(shù)值相除,并將結(jié)果壓入棧頂):
          操作數(shù)棧:
          局部變量表:

          經(jīng)過(guò)上面的操作(1/0)拋出異常,此時(shí)根據(jù)異常表,執(zhí)行第10行的字節(jié)碼

          astore_1(將棧頂引用型數(shù)字存入變量表第2個(gè)本地變量,因?yàn)闂m敒榭眨远紴榭?:
          操作數(shù)棧:
          局部變量表:

          iconst_2(將int類型數(shù)字2放入操作數(shù)棧頂)
          操作數(shù)棧:?2
          局部變量表:


          istore_0(將操作數(shù)棧頂int型數(shù)字出棧存入變量表第1個(gè)本地變量):
          操作數(shù)棧:?
          局部變量表:2


          iload_0(將變量表第1個(gè)int型本地變量推送至棧頂):
          操作數(shù)棧:?2
          局部變量表:

          istore_2(將操作數(shù)棧頂int型數(shù)字出棧存入變量表第1個(gè)本地變量):
          操作數(shù)棧:?
          局部變量表:2

          iconst_3(將int類型數(shù)字3放入操作數(shù)棧頂):
          操作數(shù)棧:?3
          局部變量表:2

          istore_0(將操作數(shù)棧頂int型數(shù)字出棧存入變量表第1個(gè)本地變量):
          操作數(shù)棧:?
          局部變量表:3

          iload_0(將變量表第1個(gè)int型本地變量推送至棧頂):
          操作數(shù)棧:?3
          局部變量表:

          ireturn(從棧頂返回int型數(shù)字,方法結(jié)束):
          返回3

          經(jīng)過(guò)上面的分析我們可以知道,try-catch-finally就是很普通的指令跳轉(zhuǎn)而已,我們最需要記住的是當(dāng)return的時(shí)候,實(shí)際上并不會(huì)馬上return,而是會(huì)將這個(gè)結(jié)果存入這個(gè)臨時(shí)變量,然后再返回這個(gè)臨時(shí)變量。

          由于本文所舉例的代碼中使用的是基本類型,所以對(duì)值的修改看上去沒(méi)有起作用,但是如果“i”是對(duì)可變類對(duì)象的引用,并且對(duì)象的內(nèi)容在finally塊中進(jìn)行了更改,則這些更改也將反映在返回的值中。


          瀏覽 54
          點(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>
                  91正在播放| 北条麻妃熟女60分钟 | 亚洲色吧色婷婷 | 欧美高清操逼视频 | 无码人妻一区二区三区综合另类 |