问题

下面的第二个问题来源于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. 【剑指Offer】调整数组顺序使奇数位于偶数前面 解题报告(Python)

    [牛客网]调整数组顺序使奇数位于偶数前面 解题报告 标签(空格分隔): 牛客网 题目地址:https://www.nowcoder.com/questionTerminal/beb5aa231adc4 ...

  2. javascript原始值和引用值类型及区别

    原始值和引用值类型及区别 首先,原始值和引用值类型都是js中的数据类型,为了充分利用存储空间,定义了不同的数据类型,而且js是弱类型,动态语言,数据类型可变. 原始值(简单数据类型) 存储在栈中的简单 ...

  3. 面试造火箭系列,栽在了cglib和jdk动态代理

    "喂,你好,我是XX巴巴公司的技术面试官,请问你是张小帅吗".声音是从电话那头传来的 "是的,你好".小帅暗喜,大厂终于找上我了. "下面我们来进行一 ...

  4. java泛型中<?>和<T>

    T 代表一种类型 加在类上==============>class SuperClass<A>{} 加在方法上============>public <T>void ...

  5. 【C++】leetcode竞赛笔记

    *注--代码非博主本人所写,仅供学习参考,侵删 20200516双周赛: 1 to_string(int a) 将a转换成字符串形式 2 gcd(int a,int  b) 返回a,b的最大公约数,若 ...

  6. 【服务器】【环境搭建】WordPress建立数据库连接时出错---问题---解决

    这意味着您在wp-config.php文件中指定的用户名和密码信息不正确,或我们未能在localhost联系到数据库服务器.这可能意味着您主机的数据库服务器未在运行. 您确定用户名和密码正确吗? 您确 ...

  7. 第四个知识点 P类复杂问题

    第四个知识点 P类复杂问题 原文地址:http://bristolcrypto.blogspot.com/2014/10/52-things-number-4-complexity-class-p.h ...

  8. SpringBootAdmin-使用踩坑

    一.版本选择 目前项目中使用的springcloud版本为 Hoxton.SR3,使用的spring-cloud-alibaba版本为2.2.1.RELEASE, 使用的springboot版本为2. ...

  9. Java的jar包构建成docker镜像并运行

    结构如下 把jar和Dockerfile放到一个文件,不在一个文件下会报错文件找不到 创建一个构建文件 buildimage.sh vi /home/hanby/buildimage.sh echo ...

  10. nginx 配置 ^~ 的用法妙处,403阻断

    看看这个 location /css/ { alias D:/我的项目/2-代码/express/src/main/resources/static/css/ ; } 咋一看貌似没有毛病,访问 htt ...