问题

下面的第二个问题来源于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】785. Is Graph Bipartite? 解题报告(Python)

    [LeetCode]785. Is Graph Bipartite? 解题报告(Python) 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu. ...

  2. 【LeetCode】71. Simplify Path 解题报告(Python)

    [LeetCode]71. Simplify Path 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://f ...

  3. 教学日志:javaSE-java中的数据类型和运算符

    一.java中的标识符 /* 标识符的命名规范: 硬性要求: 1.必须以字母._下划线.美元符$开头 2.其它部分可以是字母.下划线"_".美元符"$"和数字的 ...

  4. Reliable evaluation of adversarial robustness with an ensemble of diverse parameter-free attacks

    目录 概 主要内容 Auto-PGD Momentum Step Size 损失函数 AutoAttack Croce F. & Hein M. Reliable evaluation of ...

  5. NFS 部署

    目录 NFS 部署 NFS简介 NFS应用 NFS工作流程图 NFS部署 服务端 客户端 测试NFS文件同步功能 NFS配置详解 NFS部分参数案例 统一用户 搭建考试系统 搭建步骤 配合NFS实现文 ...

  6. partial write bypass PIE

    第一篇博客,请允许我水一下.BUUCTF上的一题:linkctf_2018.7_babypie 检查一下保护机制: 1 Arch: amd64-64-little 2 RELRO: Partial R ...

  7. 【MySQL作业】DDL 和 DML——美和易思使用 DML 删除表数据应用习题

    点击打开所使用到的数据库>>> 删除客户"刘一鸣". 执行 SQL 代码"delete from customer where cName=' 刘一鸣 ...

  8. 编写Java程序,使用JTable表格组件展现人员信息列表

    返回本章节 返回作业目录 需求说明: 使用JTable组件显现人员信息列表 实现思路: 创建一个JTable对象. 创建一个JScrollPane对象(显示横向和纵向滚动条). 将表格添加到滚动面板. ...

  9. MATLAB 右键该文件不存在或者SIMULINK打开后仿真器件报错

    错误示例: 提示是否创建文件 并且会提示某某模块或者某某文件未找到not found 原因: .m文件不存在,是因为所打开的文件还并没有添加进matlab的搜索目录,或者是需要.m文件运行后生成的 某 ...

  10. python连接真机或模拟器

    前言: 最近写自动化代码的时候,使用模拟器运行 python + appium代码时,APP闪退了,只能使用真机运行代码了.真机要怎么配置设备的信息呢? 怎么配置设备的信息? 配置是使用真机还是模拟器 ...