三年前我做错的那一道面试题

三年前,我做了一道关于try-catch-finnaly的面试题,但我做错了,当时面试官问我为啥错了,我告诉它,我平常不会写这么傻逼的代码,然后面试官就没有问我了。。。。

最近看到其他面试的童鞋,又让我想起了这道题,刚好也试着分析下。

我们知道Java虚拟机栈是线程私有的,它的生命周期与线程相同。虚拟机栈是Java虚拟机运行时数据区一部分,它描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程就对应着一个帧栈在虚拟机中入栈到出栈的过程。

加载和存储指令用于将数据在帧栈中的局部变量和操作数栈之前来回传输,这类指令包括如下内容:

  1. 将一个局部变量加载到操作栈:iload, iload_<n>、aload、aload_<n>等

  2. 将一个数值从操作数栈存储到局部变量表: istore、 istore_<n>、astore、astore_<n>等

  3. 将一个常量加载到操作数栈: ldc、iconst_<i>等

比如我们下面的代码

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

  }

}

上面的代码输出结果是1,我们首先来看看它的字节码是怎样的?

 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
  }

我来根据字节码来分析下是代码是如何运行的:

操作数栈: 先进后出的一个数据结构
局部变量表: 可以认为是一个数组,下标从0开始

以下是执行每一条指令的时候操作数栈和局部变量表的变化情况:

iconst_1(将int类型数字1放入操作数栈顶):
操作数栈: 1
局部变量表:

istore_0(将操作数栈顶int型数字出栈存入变量表第1个本地变量):
操作数栈: 
局部变量表:1

iload_0(将变量表第1个int型本地变量推送至栈顶):
操作数栈: 1
局部变量表:

istore_1(将操作数栈顶int型数字出栈存入变量表第2个本地变量):
操作数栈:
局部变量表: null 1

iconst_3(将int类型数字3放入操作数栈顶):
操作数栈: 3
局部变量表: null 1

istore_0(将操作数栈顶int型数字出栈存入变量表第1个本地变量):
操作数栈: 
局部变量表: 3 1

iload1(将变量表第2个int型本地变量推送至栈顶):
操作数栈: 1
局部变量表: 3

ireturn(从栈顶返回int型数字,方法结束):
返回1

可以看到ireturn后面的代码就不会被执行了,我们也就不进行翻译了。实际上try-catch-finally字节码块中是没有finally的,根据字节码我们可以将代码简化成这样

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

我相信你能知道输出的结果是3,我们同样来看下字节码是怎样的?

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
}

同样的我们只分析执行到的部分

iconst_1(将int类型数字1放入操作数栈顶):
操作数栈: 1
局部变量表:

istore_0(将操作数栈顶int型数字出栈存入变量表第1个本地变量):
操作数栈: 
局部变量表:1

iload_0(将变量表第1个int型本地变量推送至栈顶):
操作数栈: 1
局部变量表:

istore_1(将操作数栈顶int型数字出栈存入变量表第2个本地变量):
操作数栈: 
局部变量表: null 1

iconst_3(将int类型数字3放入操作数栈顶):
操作数栈: 3
局部变量表:null 1

istore_0(将操作数栈顶int型数字出栈存入变量表第1个本地变量):
操作数栈: 
局部变量表:3 1

# 注意这里和上面字节码的不同之处在于上面是加载变量表中的第二个int类型本地变量
iload_0(将变量表第1个int型本地变量推送至栈顶):
操作数栈: 3
局部变量表:1

ireturn(从栈顶返回int型数字,方法结束):
返回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;
  }
}

之前的代码很明显不会抛出异常,所以就用不到异常表中的内容,但是这里肯定是产生异常(1/0),而在java中对异常的处理在字节码层面是使用Exception Table来完成的。

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
}

这个异常表(Exception table)含义是如果当字节码在第from行到第to行之间(不包含to行)出现了类型为type或者其子类的异常则转到第target行继续处理。当type的值为any时,代表任意异常情况都需要转向到target处进行处理。

异常表也相当于指明了代码有可能的分支执行情况。老规矩,我们一行一行来分析字节码

iconst_1(将int类型数字1放入操作数栈顶):
操作数栈: 1
局部变量表:

iconst_0(将int类型数字0放入操作数栈顶):
操作数栈: 0 1
局部变量表:

idiv(将操作数栈顶两int型数值相除,并将结果压入栈顶):
操作数栈:
局部变量表:

经过上面的操作(1/0)抛出异常,此时根据异常表,执行第10行的字节码

astore_1(将栈顶引用型数字存入变量表第2个本地变量,因为栈顶为空,所以都为空):
操作数栈:
局部变量表:

iconst_2(将int类型数字2放入操作数栈顶)
操作数栈: 2
局部变量表:


istore_0(将操作数栈顶int型数字出栈存入变量表第1个本地变量):
操作数栈: 
局部变量表:2


iload_0(将变量表第1个int型本地变量推送至栈顶):
操作数栈: 2
局部变量表:

istore_2(将操作数栈顶int型数字出栈存入变量表第1个本地变量):
操作数栈: 
局部变量表:2

iconst_3(将int类型数字3放入操作数栈顶):
操作数栈: 3
局部变量表:2

istore_0(将操作数栈顶int型数字出栈存入变量表第1个本地变量):
操作数栈: 
局部变量表:3

iload_0(将变量表第1个int型本地变量推送至栈顶):
操作数栈: 3
局部变量表:

ireturn(从栈顶返回int型数字,方法结束):
返回3

经过上面的分析我们可以知道,try-catch-finally就是很普通的指令跳转而已,我们最需要记住的是当return的时候,实际上并不会马上return,而是会将这个结果存入这个临时变量,然后再返回这个临时变量。由于本文所举例的代码中使用的是基本类型,所以对值的修改看上去没有起作用,但是如果“i”是对可变类对象的引用,并且对象的内容在finally块中进行了更改,则这些更改也将反映在返回的值中。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345