有关java类、对象初始化的话题,从一道面试题切入
2021-05-16 21:28
标签:sub 实例变量 rgs 学习 html 生命周期 代码 类变量 介绍 最近在整理东西时,刚好碰到以前看的一道有关java类、对象初始化相关题目,觉得答案并不是非常好(记忆点比较差,不是很连贯)。加上刚好复习完类加载全过程的五个阶段(加载-验证-准备-解析-初始化),所以如果周志明大大诚不我欺的话,无论是类加载过程、还是实例化过程的顺序我都已经了然于心了才对。 标题:2015携程JAVA工程师笔试题(基础却又没多少人做对的面向对象面试题) 地址:https://zhuanlan.zhihu.com/p/25746159 该题代码如下: 问题:main函数运行后输出什么? 输出base?sub?还是null? 这个题从第一次搜集,到复习,两遍......在不看答案的情况下愣是没想起来......好在COPY AND PASTE工程师有一样法宝就是“能run 的先 run一遍再说”。憋着不看答案,run完之后,结果是null。不是很震惊,因为如果是base或sub的话,这道题的层次就不会很高。 嗯,那为什么是null呢?看一下原链接给的提示(文末总结)是什么: 看完之后,一口老血吐了出来,错别字先不提,主要是记忆点比较模糊,像这种“父类静态块 ->子类静态块 ->父类初始化语句”,还有这种“父类构造函器 ->子类初始化语句 子类构造器”,怎么记?死记硬背吗?但是顺序是没有错的,每个位置的表达上还是有些问题。 看得出来答主是按照本题的代码进行分析,最后我也会给出一个执行顺序和原文总结中顺序一致的代码出来,其实就是把静态语句块(static{}),实例初始化块({}),Sub类的无参构造器补齐之后(同时调整一下类的位置)观察它们的输出顺序的结果。 工欲善其事必先利其器,这里有必要把一些概念理清楚。 (1)类初始化阶段,和 实例初始化 因为类加载有“初始化”阶段,而实例初始化也常被工程师们叫做“初始化”,因此,为了区分这两个“初始化”,类加载过程的初始化我叫它“类初始化阶段”,实例初始化过程我叫它“实例初始化”(不用简称,免得混乱)。 先看下类加载的初始化阶段到底是什么: 上图是《深入java虚拟机》周志明大大的第二版书,第7章第2节内容的第一张图。这7个阶段中和程序员比较有关系的是loading、preparation和initialization。后面会讲到。 类的初始化阶段是其生命周期中第5个阶段。让我们看看这个阶段干了些什么:初始化阶段是执行类构造器 (2)静态语句块(static{}),和 实例初始化块({}) 原题代码中没有书写static{} 也没有 {} 。后面我的补充代码会展示出来。 (3)类加载,和 类初始化 所谓的类加载应该指的是类生命周期中的前五个阶段,从loading到initialization。在《深入java虚拟机》书中7.2节提到的五个必须进行“初始化”的情况中就包括“先初始化父类”这一条。通常在泛指的时候,可以把“类加载”和“类初始化”对等起来,它们表示类声明周期的前五个阶段,这样在口头表述的时候比较便利。 将类初始化阶段和实例初始化这两个概念区分开后,再分析这个题目。 main函数中的代码非常简单:Base b = new Sub(); 。但是注意,这行代码是在Base.java文件的main方法中,而运行main函数,JVM首先要加载这个main函数所在的类,即运行这行代码时JVM会先加载Base到方法区中,可能会对加载顺序的判断有一丢丢影响(所以后面我重写了代码,Base和Sub放在Xase中,让Xase类运行main方法)。 好,绕了一点弯路,重新回到题目上,前面提到最近刚好复习了类加载过程的五个阶段(生命周期中到“使用”之前的那五个阶段),不知道书上有没有提到实例初始化过程?这个阶段涉及到如本题中main函数所示的代码如何初始化(b的静态类型是Base,实际类型是Sub),不过其实也就是子类覆盖父类方法如何执行的问题,剩下的那些实例变量和实例初始化块执行顺序都是很容易理解的。 经过自己debug(原文中也提到)也能发现Sub类覆盖Base的callName后实际运行时该方法的指针是指向Sub的callName方法的(debug时将看到执行Base构造函数中的callName时会跳转到Sub的callName方法上)。所以到这里可以“假装”我们也掌握了实例初始化相关的知识点。 有了以上两个知识点,开始分析代码,在main函数中执行代码Base b = new Sub(); JVM会按顺序将 Base、Sub类的class文件加载到方法区(完成生命周期的前五个阶段)。由于原文中main方法在Base中,所以Base已经加载过,所以执行这段代码时,只是加载Sub的class文件(后面加载类简称加载)。JVM如果要加载Sub类,要先保证它的父类Base被加载。因此无论是代码顺序保证的,还是类加载机制保证的,Base类都会先于Sub类被加载到方法区。 (1)加载类阶段 好了,目前为止Base和Sub都加载完了,由类加载机制保证父类先加载,所以 父类的类初始化 先于 子类的类初始化 完成。 即,实例初始化前,有 父类的类初始化 -> 子类的类初始化。 前面说过, 本题中没有类变量,但学习时我们可以自己加上,后面我会再贴出另一道类初始化和实例化相关的题,该题中你可以认识到 static语句的赋值操作语句 和 非赋值操作语句 是如何被 (2)实例化阶段 接下来执行 new Sub(); 由于Sub没有定义构造函数,JVM生成一个默认构造函数(无参构造函数)。子类的任意构造函数中都有一个隐式的super()来调用父类的默认构造函数,保证继承的实例字段正确初始化。如果父类自己写了带参的构造函数,子类构造函数要明确指定调用一个父类的构造函数。 这里,两个类都是无参构造函数,所以Sub的默认构造函数可以调用到Base的无参构造函数。 到这里可以明确的关系有:父类的类初始化 -> 子类的类初始化 -> ...... -> 父类构造函数 -> ...... -> 子类构造函数。 你会看到每次推导出来的顺序阶段都加上蓝色字体,便于比较。 类变量的初始化过程上面介绍过,叫做 因为 那么 那么 接下来请允许我用 因此,目前可以确定的调用顺序为(加上标号):①父类的类初始化 -> ②子类的类初始化 -> ③父类的 前面分析到执行Sub构造函数之前,会先调用Base的构造函数,在Base构造函数中调用前, Base的 查阅《深入java虚拟机》7.3.3节,在 准备阶段(对应上述①),生命周期的第3个阶段 会为 类变量 赋“零值”,但是 baseName的定义不是 类变量,只是普通的实例变量,可以套用这个规则吗? 既然 继续分析,Base的构造函数(对应上述④)又调用了 callName方法,由于Sub类重写了Base类的 callName,所以 debug时可以看到跳转到Sub的 callName中,此时,程序只运行到上述④的阶段,Sub类的 接着,Base构造函数调用结束,进入上述 ⑤ 和 ⑥ 的执行过程。执行完 ⑤ 后 Sub的 baseName = "sub"。⑥ 过程没有执行任何东西,整个过程就结束了。 通过这道面试题的学习,你应该了解到,new ClassName(); 其实分两个大的阶段,第一个是先加载类(五个阶段)到方法区,对应上述①和②两个阶段;第二个才是使用加载好的class对象创建实例(实例初始化过程),对应上述③~⑥ 四个阶段。 最后调整过后的代码如下: Xase包裹了Base和Sub,作为一道面试题“拥挤”一点也不是什么毛病。其中main方法在上述代码中放在Xase类中,如果你不想显示Xase的静态代码块的打印,可以把main方法挪到Sub方法里。 debug时,在每一个 {} ,static{} ,构造函数,实例变量 和 类变量上都打上断点,就可以清楚观察到对象实例化时各个代码调用的顺序。 附我的Xase代码输出: 说好的附加题,http://www.cnblogs.com/javaee6/p/3714716.html 这道题中可以直接应用《深入理解java虚拟机》7.3.3节的零值规则,大不了不对还可以吐槽一下周大大。分析的过程也相对少一些(比开头那题少40%~50%分析量吧)。 掌握了真正的分析方法后,这种类型的题已经不是什么问题。 其实,要全面一点的话,内部类那一块的初始化也应该练一练,但是之前没有找到好的题目,如果大家有好的题目可以实战,欢迎在评论区砸我~哈哈 有关java类、对象初始化的话题,从一道面试题切入 标签:sub 实例变量 rgs 学习 html 生命周期 代码 类变量 介绍 原文地址:https://www.cnblogs.com/christmad/p/9747187.html一道面试题
public class Base {
private String baseName = "base";
public Base() {
callName();
}
public void callName() {
System.out.println(baseName);
}
static class Sub extends Base {
private String baseName = "sub";
public void callName() {
System.out.println(baseName);
}
}
public static void main(String[] args) {
Base b = new Sub();
}
}
关于类初始化、实例化的问题该如何分析
整理概念
分析题目
总结
/**
* https://zhuanlan.zhihu.com/p/25746159
* 2015携程JAVA工程师笔试题(基础却又没多少人做对的面向对象面试题)
*/
public class Xase {
{
System.out.println("I‘m Xase {}");
}
static {
System.out.println("I‘m Xase static {}");
}
static class Base {
public Base() {
System.out.println("Base constructor");
callName();
}
{
// base的实例初始化块
System.out.println("BASE {}");
}
private String baseName = "base";
static {
// base的静态语句块
System.out.println("Base static {}");
}
public void callName() {
System.out.println(baseName);
}
}
static class Sub extends Base {
private String baseName = "sub";
public Sub() {
System.out.println("Sub constructor");
}
{
System.out.println("Sub {}");
b = 3;
}
private int a = 2;
private int b;
static {
// sub的静态语句块
System.out.println("Sub static {}");
}
public void callName() {
System.out.println(baseName);
}
}
public static void main(String[] args) {
Base b = new Sub();
}
}
最后的最后
上一篇:【转】CentOS7.2安装python2.7.12
下一篇:数组的迭代方法