问题

下面的第二个问题来源于Oracle的笔试题, 非常经典的一个问题, 我从07年开始用了十几年. 看似简单, 做对的比例不到2/10.

  • 描述一下多级继承中类的构造顺序
  • 给定两段代码, 分别是父类和子类, 写出(或选择)正确的输出

    代码如下
public class Base {
public Base() {
method(100);
}
public void method(int i) {
System.out.println("Base::method " + i);
}
} public class Sub extends Base {
public Sub() {
super.method(70);
}
public void method(int j) {
System.out.println("Sub::method " + j);
}
public void method(String j) {
System.out.println("Sub::passed " + j);
}
public static void main(String[] args) {
Base b1 = new Base();
Base b2 = new Sub();
}
}

分析

这是属于Java中常见的基础概念问题, 正确回答这些问题, 需要对类的这些知识有清晰的了解:

  1. Java类实例的初始化顺序
  2. Java类方法的重写, 以及和重载的区别

以下通过具体场景说明

场景一

先看下面代码, 执行Sub.java时的屏幕输出是什么?

Base.java

public class Base {
public Base() {
method(100);
}
public void method(int i) {
System.out.println("Base::method " + i);
}
}

Sub.java

public class Sub extends Base {
public void method(int j) {
System.out.println("Sub::method " + j);
}
public static void main(String args[]) {
Base b2 = new Sub();
}
}

这里有两个很重要的概念:

  1. 类初始化时隐藏的构造顺序: 先调用父类的默认构造函数(即不带参数的构造函数), 然后再执行当前构造函数, 因为本例中Sub.java的构造函数为空(未定义), 因此实际执行的是父类的默认构造函数
  2. 父类的函数, 会被子类定义的同参数方法覆盖, 这叫方法重写. 本例中初始化的类是Sub, Sub中的method(int i), 已经重新定义, 因此父类的默认构造函数中调用的是Sub的method(int i).

输出

Sub::method 100

场景二

保持父类Base.java不变, 在Sub.java中增加子类的构造函数, 执行Sub.java时的屏幕输出是什么?

public class Sub extends Base {
public Sub() {
super.method(70);
}
public void method(int j) {
System.out.println("Sub::method " + j);
}
public static void main(String args[]) {
Base b2 = new Sub();
}
}

第一行的输出可以参照场景一的说明, 初始化Sub时, 会隐藏调用父类的默认构造函数, 第二行则是子类构造函数中, 在super.method(70);中指定使用父类的method(int i)方法产生的结果.

这个输出验证了前面说的类初始化时的隐藏初始化顺序: 会先调用父类的默认构造函数(即不带参数的构造函数), 然后再执行当前构造函数里的逻辑. 这是最容易出错地方, 漏了第一行, 忘记了即使子类定义了构造函数, 父类构造函数一样会执行.

输出是

Sub::method 100
Base::method 70

场景三

再验证一下类初始化时的隐藏初始化顺序, 如果父类和子类都增加了带变量的构造函数, 执行Sub.java时的屏幕输出是什么?

Base.java

public class Base {
public Base() {
method(100);
}
public Base(int i) {
method(i);
}
public void method(int i) {
System.out.println("Base::method " + i);
}
}

Sub.java

public class Sub extends Base {
public Sub(int i) {
super.method(70);
}
public void method(int j) {
System.out.println("Sub::method " + j);
}
public static void main(String args[]) {
//Base b1 = new Base();
Base b2 = new Sub(100);
}
}

依然先调用了父类的默认构造函数(不带参数), 再调用子类的构造函数, 输出是

Sub::method 100
Base::method 70

场景四

父类不带默认构造函数

Base.java

public class Base {
public Base(int i) {
method(i);
}
public void method(int i) {
System.out.println("Base::method " + i);
}
}

此时Sub.java如果还使用前一个场景的代码, 就会无法通过编译, 因为找不到父类的默认构造函数了, 此时隐藏的初始化顺序就失效了, 需要指定使用哪个父类构造函数

Sub.java

public class Sub extends Base {
public Sub(int i) {
super(100); // <------- 需要显式指定构造函数
super.method(70);
}
public void method(int j) {
System.out.println("Sub::method " + j);
}
public static void main(String args[]) {
//Base b1 = new Base();
Base b2 = new Sub(100);
}
}

执行Sub.java时的屏幕输出依然是

Sub::method 100
Base::method 70

总结

问题: 描述一下多级继承中类的构造顺序

解答:

  1. 类初始化时的隐藏逻辑: 先调用父类的默认构造函数, 再调用子类的默认构造函数
  2. 当父类定义了带参数的构造函数, 又不定义默认构造函数时, 上一条就失效了, 子类必须显式指定父类的构造函数

Java语法专题1: 类的构造顺序的更多相关文章

  1. Java语法专题2: 类变量的初始化顺序

    合集目录 Java语法专题2: 类变量的初始化顺序 问题 这也是Java面试中出镜率很高的基础概念问题 描述一下多级继承中字段初始化顺序 描述一下多级继承中类变量初始化顺序 写出运行以下代码时的控制台 ...

  2. c++ 类的构造顺序

    在单继承的情况下,父类构造先于子类,子类析构先于父类,例: class A { public: A() { cout << "A" << endl; } ~ ...

  3. 从Java虚拟机角度分析类的实例化顺序

    1.首先展示一下实例代码(Son.java & Father.java) public class Father { public static int a=10;//父类的静态变量 stat ...

  4. c# 衍生类和基类的构造顺序

    public class MyDeriveClass :MyBaseClass { public MyDeriveClass() :base() { } int derive_int = 1; } p ...

  5. java类的初始化顺序

    在java中,当我们new一个对象时,对象中的成员,初始化块以及构造方法的加载是有一定的顺序的,看下面一副图: 一.单类(无基类)下的初始化顺序: public class Parent { stat ...

  6. java中带继承类的加载顺序详解及实战

    一.背景: 在面试中,在java基础方面,类的加载顺序经常被问及,很多时候我们是搞不清楚到底类的加载顺序是怎么样的,那么今天我们就来看看带有继承的类的加载顺序到底是怎么一回事?在此记下也方便以后复习巩 ...

  7. C++类继承中,基类/当前对象属性/当前对象的构造顺序

    [1]中提到,规范的派生类构造函数三个要点: 首先创建基类对象 应通过成员初始化列表,创建基类对象 应该初始化本派生类新增的成员变量 那在构造派生类实例的过程中,其基类(以及多继承的时候多个基类)/当 ...

  8. java学习(一)静态代码块 构造代码块 构造方法的执行顺序及注意问题

    今天我总结了一下java中静态代码块 构造代码块 构造方法的执行顺序及其注意问题 首先要知道静态代码块是随着类的加载而加载,而构造代码块和构造方法都是随着对象的创建而加载 当时做了这么一个小案例(想必 ...

  9. 比较C++、Java、Delphi声明类对象时候的相关语法

    同学们在学习的时候经常会遇到一些问题,C++.Java.Delphi他们到底有什么不一样的呢?今天我们来比较C++.Java.Delphi声明类对象时候的相关语法.希望对大家有帮助! C++中创建对象 ...

随机推荐

  1. 【LeetCode】692. Top K Frequent Words 解题报告(Python)

    [LeetCode]692. Top K Frequent Words 解题报告(Python) 标签: LeetCode 题目地址:https://leetcode.com/problems/top ...

  2. D. Ability To Convert

    http://codeforces.com/contest/758/problem/D D. Ability To Convert time limit per test 1 second memor ...

  3. 【自编教材】16万8千字的HTML+CSS基础 适合从0到1-可收藏

    [图片链接有点小问题,这几天更新,敬请期待!] 目 录 第一章HTML基础 1.1 HTML简介和发展史 1.1.1 什么是HTML 1.1.2 HTML的发展历程 1.1.3 web标准 1.2 开 ...

  4. 【汇编语言】李忠《x86汇编语言——从实模式到保护模式》

    该书配套资料网址已经失效 配套资料和章节答案下载 查看最新作者网址:http://www.lizhongc.com/ 勘误表:https://wenku.baidu.com/view/9213288b ...

  5. 「双串最长公共子串」SP1811 LCS - Longest Common Substring

    知识点: SAM,SA,单调栈,Hash 原题面 Luogu 来自 poj 的双倍经验 简述 给定两字符串 \(S_1, S_2\),求它们的最长公共子串长度. \(|S_1|,|S_2|\le 2. ...

  6. C++中常用的数学函数总结

    我们在C++程序设计的过程中往往会使用到一些数学函数,那么不同的数学运算要用到什么函数哪?大家可以参考我的总结如下: 首先引用到数学函数时一定要记得加函数头文件 #include<cmath&g ...

  7. EMA

    目录 源 设置 结果 源 Exponential moving average (EMA) 是一个非常有用的trick, 起到加速训练的作用. 近来发现, 该技巧还可以用于提高网络鲁棒性(约1% ~ ...

  8. BAIRE ONE FUNCTIONS (Baire第一类函数)

    目录 定义 导函数 一致收敛性质 的连续点 JOHNNY HU, BAIRE ONE FUNCTIONS. 一些基本的定义(诸如逐点收敛, 一致收敛\(F_{\sigma}\)集合等)就不叙述了. 定 ...

  9. CS5213设计HDMI转VGA带音频信号输出|CS5213方案|CS5213设计电路

    CS5213是一款用于设计HDMI转VGA音视频信号转换器方案,CS5213设计HDMI转VGA转换器或者转接线产品特点: 将完整的HDMI信号转换为VGA输出支持数字信号到模似信号的转换支持 HDC ...

  10. ORW-测信道攻击

    做SCTF时碰到一个没看过的题型,比赛结束之后才知道是orw的一个玩法,测信道攻击.主要特点就是只给使用open,read,但是不给write,即无法把flag输出到终端.这里可以通过把flag读到栈 ...