写点什么

【Java 试题】从一道题目再看 Java 继承

发布于: 2021 年 04 月 06 日
【Java试题】从一道题目再看Java继承

一 题目

一道 Java 基础题:两个类,ClassB 继承自 ClassA,代码如下,判断输出内容:

ClassA:

public class ClassA {
public String toString(){ return "C"; }
{ System.out.println("B"); }
static { System.out.println("A"); }
}
复制代码

ClassB:

package com.learn.jdk.basic;
public class ClassB extends ClassA { public String toString(){ return "F"; }
{ System.out.println("E"); }
static { System.out.println("D"); }
public static void main(String[] args){ System.out.println(new ClassB()); }}
复制代码

如上代码所示,判断 ClassB 的输出内容及输出顺序。

二 分析

2.1 关键信息

题目中的关键要素:继承、代码块、静态代码块、toString()方法重写。

我们先看 ClassB,main 方法中,通过 System.out.println() 打印 ClassB 的实例内容。这里我们可以比较容易分析出是先 new ClassB(), 然后在输出前隐式调用了 ClassB 的 toString()方法。ClassB 继承自 ClassA,但因为重写了 toString(),所以 toString()方法会输出 F;

new ClassB()的动作在前,并且 ClassB 有两个代码块,一个非静态(构造)代码块,一个静态代码块;初步猜测执行顺序是先静态代码块,后构造代码块。只看 ClassB,那么输出顺序应该是 D E F。接下来 ClassA 中的代码块,是否会输出,以及会怎样输出?

2.2 代码块

2.2.1 代码块类型

我们先回顾 Java 代码块的基本概念和四种类型:

2.2.1.1 普通代码块

在方法或语句中出现的{}就称为普通代码块。普通代码块和一般语句的执行顺序由他们在代码中出现的次序决定,先出现先执行。

2.2.1.2 构造代码块

直接在类中定义且没有加 static 关键字的代码块称为{}构造代码块。构造代码块在创建对象时被调用,每次创建对象都会被调用,并且构造代码块的执行次序优先于类构造函数。如果存在多个构造代码块,执行顺序由他们在代码中出现的次序决定,先出现先执行。

2.2.1.3 静态代码块

在 java 中使用 static 关键字声明的代码块。静态块用于初始化类,为类的属性初始化。每个静态代码块只会执行一次。由于 JVM 在加载类时会执行静态代码块,所以静态代码块先于主方法执行。如果类中包含多个静态代码块,那么将按照"先定义的代码先执行,后定义的代码后执行"。

注意:

1.静态代码块不能存在于任何方法体内。

2.静态代码块不能直接访问实例变量和实例方法,需要通过类的实例对象来访问。

2.2.1.4 同步代码块

使用 synchronized(){}包裹起来的代码块,在多线程环境下,对共享数据的读写操作是需要互斥进行的,否则会导致数据的不一致性。同步代码块需要写在方法中

2.2.2 静态代码块和构造代码块的异同点

相同点:都是 JVM 加载类后且在构造函数执行之前执行,在类中可定义多个,一般在代码块中对一些 static 变量进行赋值。

不同点:静态代码块在非静态代码块之前执行。静态代码块只在第一次 new 时执行一次,之后不在执行。而非静态代码块每 new 一次就执行一次

2.3 继承

2.3.1 定义

从 2.2.1 和 2.2.2 的内容,可以验证我们对 ClassB 部分的输出猜测。下一步,继承。ClassB 继承自 ClassA,回顾继承定义:

继承使用的 extends 关键字,子类会继承父类的所有成员变量和方法。当我们创建一个类的时候,无时无刻不在继承,如果我们没有明确的指出继承哪个类,隐式的继承 Object 类。

Java 使用 super 关键字来调用父类的构造方法。在子类继承父类的时候,会自动调用父类的构造函数,如果都是无参的构造函数,则不需要显示调用,如果是有参的构造函数,子类继承的时候就需要显示的用 super 调用。

2.3.2 再次分析

基于 2.3.1 的概念理解,我们再回到题目。new ClassB()时,因为在 ClassB 内没有编写构造方法,所以实际上会调用 ClassA 的构造方法,这时也会执行 ClassA 中的静态代码块和普通代码块。换个角度理解,ClassB 继承自 ClassA,继承的是非 private 属性和方法,那么也包含代码块,只不过静态代码块只在第一次 new 时调用一次,普通代码块每次 new 时调用一次。

那么,根据上述内容,ClassB 在创建实例时,执行顺序就是:

1、ClassA 的静态代码块,打印 “A”;

2、ClassB 的静态代码块,打印 “D”(静态代码块部分);

3、ClassA 的普通代码块,打印“B”;

4、ClassB 的普通代码块,打印“E”(至此构造过程结束);

5、toString()方法,打印“F”。

执行 ClassB 的 main 方法,查看控制台输出:

由此确认打印顺序推测无误。

三 总结

老实说,遇到这样的题目,开始有些不知所措,因为太过基础,基础到以前没有想过去验证。但如果对相关基础概念有比较深的理解的话,还是可以推测出来的。借此机会,再夯实以下,也不算是坏事。

发布于: 2021 年 04 月 06 日阅读数: 26
用户头像

磨炼中成长,痛苦中前行 2017.10.22 加入

微信公众号【程序员架构进阶】。多年项目实践,架构设计经验。曲折中向前,分享经验和教训

评论

发布
暂无评论
【Java试题】从一道题目再看Java继承