记一道 Java 综合面试题

前言

昨天看了一道关于异常处理的题:由一道 Java finally 执行顺序的题引发的思考,今天又在牛客网看到一道更深的题,这次加上了一个子父类继承关系的知识点。

原题

public class Test {
    public static void main(String[] args) {
        System.out.println(new B().getValue());
    }

    static class A {
        protected int value;

        public A(int v) {
            setValue(v);
        }
        public void setValue(int value) {
            this.value = value;
        }
        public int getValue() {
            try {
                value++;
                return value;
            } catch (Exception e) {
                System.out.println(e.toString());
            } finally {
                this.setValue(value);
                System.out.println(value);
            }
            return value;
        }
    }
    static class B extends A {
        public B() {
            super(5);
            setValue(getValue() - 3);
        }
        public void setValue(int value) {
            super.setValue(2 * value);
        }
    }
}

题解

这道题考的就是代码执行顺序,比较直观的方法是使用单步调试,来一起看下执行过程吧:

首先从 main 方法开始执行,new 了一个对象 B,没什么可说的。

接下来执行到了 B 类的构造方法,也没什么问题。

然后执行到了父类的有参构造。

这里要执行 setValue(v) 方法了,那么这里的 setValue() 方法应该是执行 B 类的,还是 A 类的呢?

其实这个结果是有些出乎我意料, 怎么会执行子类 B 的 setValue() 方法呢? 因为这里正在执行 B 的构造方法中,所以调用的方法就默认是 B 的,当 B 中没有,才会去找它的父类 A 中的方法。

下一步是 super.setValue() ,这里明确指明了调用父类的方法,所以应该没啥问题。

嗯,这里将 A 类的 value 属性设值为 10。

然后还回到 A 的构造方法中。

再回到 B 的构造方法中,接下来应该调用 getValue

由于子类中没有,所以调用父类的 getValue 方法。

接下来将 A 中的 value 自增 1,然后到了 return value ,由于下面还有 finally 语句块,所以先执行 finally 语句块,再返回。

又到了一个 setValue 方法,那么这里调用的是子类 B 的,还是父类 A 的呢?

可以看到,这里调用的是子类 B 的构造方法,还是刚才的那个结论: 因为这里正在执行 B 的构造方法中,所以调用的方法就默认是 B 的,当 B 中没有,才会去找它的父类 A 中的方法。

接下来应该会执行 super.setValue() 也就是它父类 A 的 setValue() 方法。

这里会将 A 的 value 值设为 22。

然后回到刚才的方法里,准备打印 value 值,打印后一共会回到 try 里的 return 语句。

这里虽然 value 的值是 22,但是刚才执行 finally 语句块之前就已经将 reutrn 的值确定了,也就是 11,详细参见 由一道 Java finally 执行顺序的题引发的思考

回到刚才的调用,由于上一步返回的值是 11,所以这里应该是调用的setValue(11- 3);,这里调用的是 B 的方法。

接下来又要执行 super.setValue() 了,也就是父类的 setValue() 方法。

这里会将 A 类的的 value 值从 22 改为 16.

然后 B 类的构造方法执行完了,回到 main 方法,准备调用 getValue() 方法,因为 B 类没有,所以会调用其父类 A 的。

这里将 A 的 value 值自增1,变为 17 ,然后最为 return 的最终返回结果后,执行 finally 语句中的内容。

这里又到了 setValue 的抉择,到底执行哪个类的呢?

还是调到了 B 类中,因为 main 方法里调用的是 new B().xxx 方法,所以这里的 this 代表的是 B 类。 接下来这里指定 super.setValue() ,调用父类的方法,将父类的 value * 2 ,然后回到 finally 块中。

然后调用输出语句,输出结果应为 34,输出后会返回到 try 里的 return 语句中。

那么这里的 return 值应该是刚才已经确定的值,也就是 17,然后回到 main 方法

这里返回的是 17,所以输出 17,程序结束。

总结

这道题很长,不过也只是考两个知识点:

  1. 子类与父类之间的调用关系:动态分派 在调用new B()时调用A的构造器时和super.getValue()时的setValue(int value)方法是根据隐式对象的实际类型来确定的。只有实际类型未重写该方法时,才按照继承层次由下往上查找。这个可以参阅《深入理解JVM》的“分派”一节。
  2. try-catch-finally 的执行顺序,详见: 由一道 Java finally 执行顺序的题引发的思考

理解了这两个知识点。还要脑子清醒,一步一步做,应该没什么问题。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏互联网开发者交流社区

Java逻辑

15840
来自专栏chenjx85的技术专栏

leetcode-179-Largest Number(理解规则,自定义cmp函数进行排序)

1、这道题给定一个vector,里面存放着int类型的非负整数,要求把这些非负整数拼起来,尽可能拼成一个最大的整数。

20630
来自专栏静晴轩

类数组借用数组方法

于JavaScript如何将对象转化为数组对象,其用法写法已经很常见且完善,比如JQuery中的makeArray函数对此的实现,也是跟大家想的差不多,只是考虑...

38590
来自专栏前端吧啦吧啦

涨薪必备Javascript,快点放进小口袋!

32170
来自专栏前端吧啦吧啦

涨薪必备Javascript,快点放进小口袋!

14920
来自专栏GIS讲堂

面向对象的三个基本特征

封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

19630
来自专栏逸鹏说道

Python3 与 C# 基础语法对比(List、Tuple、Dict、Set专栏-新排版)

在线预览:http://github.lesschina.com/python/base/pop/3.listtupledict_set.html

30350
来自专栏闻道于事

Java之面向对象例子(一)

定义一个人类,给这个类定义一个从身份证获取生日的方法,输入身份证,获取出生年月日 //主方法 package com.hanqi.maya.model; imp...

33380
来自专栏鸿的学习笔记

python的字典和集合

dict类型可以说是python里模块的命名空间,实例的属性,函数的关键字参数都有其的参与。

11430
来自专栏Java进阶之路

Java8 Optional 的正确使用方式

21100

扫码关注云+社区

领取腾讯云代金券