• Java语法专题1: 类的构造顺序


    问题

    下面的第二个问题来源于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. 当父类定义了带参数的构造函数, 又不定义默认构造函数时, 上一条就失效了, 子类必须显式指定父类的构造函数
  • 相关阅读:
    涉猎
    linq to sql中的自动缓存(对象跟踪)
    Java的起源和发展
    Java为什么需要保留基本数据类型
    JDK各版本新增的主要特性
    【转】整理:著名软件是使用什么语言写的?
    Struts2框架学习
    json格式
    sqlserver开窗函数改造样例
    说一下这次的求职经历。
  • 原文地址:https://www.cnblogs.com/milton/p/15804995.html
Copyright © 2020-2023  润新知