摘要:对子类来讲,父类的所有东西都是准备好的,所以父类部分的初始化要先于子类。父类的初始化顺序,递归地受以上规则的控制。例如陈皓的一篇构造时成员初始化的陷阱就谈到了一个让人头痛的情况。
需要考虑的”初始化“或者说”调用“部分就4个:
member
method
特殊的method:constructor。
特殊的member:static member/block。
理解记忆方式:
对多带带一个method来讲,它所在的类已经被构建了,所以它所在类的constructor一定是已经被调用了。(甚至可以展开说,method的被调用顺序是最靠后的,因为为了实现多态,它必须要late binding)
对constructor来讲,所有的member应该都是可用的,所以member的初始化要先于constructor。(成员变量可以看作是这个类的固有属性。要构建一个类,它的固有属性肯定是要事先准备好。就好比是,你要new一个desk类,那至少,这个desk的width, height这些固有属性要准备好吧,否则,我怎么知道应该建造一个多大的desk呢?)
对member来讲,和所在类息息相关的static部分,应该在定义类的阶段,最先就被初始化,所以它会优先于其它的member。
【另一方面,要保证所有member准备好,对于子类来讲,可以调用的父类的member也是应该被”正确“准备好的,而这一条只能由父类的constructor保证。所以父类的constructor要先于子类的member被调用。】对子类来讲,父类的所有东西都是准备好的,所以父类部分的初始化要先于子类。所以,父类的constructor要先于子类的member,以保证子类的member调用的父类public部分都被事先构建好了。(但父类的constructor会晚于子类的static,这是一个例外,见下一条)
由于static是和类一起同生共死,所以,即便是子类,在看到这个名字时对应的static就已经被初始化了,所以它”甚至“优先于父类的constructor,但会晚于父类的static(子类的static可能会调用父类的static,所以必须保证父类的static先被初始化)。
父类的初始化顺序,递归地受以上规则的控制。
坑那是不是说掌握了这些原则就万事大吉了呢?我想并不是,更重要的是,你需要按照规范去编写代码。
但是,你并不总是可以遇到很规范的代码,往往会有奇怪的逻辑和用法。例如陈皓的一篇blog《JAVA构造时成员初始化的陷阱》就谈到了一个让人头痛的情况。我们做简要地考察:
public class Base { Base() { preProcess(); } void preProcess() {} } public class Derived extends Base { public String whenAmISet = "set when declared"; @Override void preProcess() { whenAmISet = "set in preProcess()"; } } public class Main { public static void main(String[] args) { Derived d = new Derived(); System.out.println( d.whenAmISet ); } }
问的是,在main()函数中,whenAmISet的值应该是什么。
有了我们上面的分析做基础,我觉得你不应该直接去回答这个问题,而是应该反问,这段代码的逻辑是否有问题。
因为在Base这个类中,构造函数竟然调用了一个method来做初始化,而且,这个method还在子类中被重写。
通过我们上面的逻辑,method的初始化应该放在最后,因为要支持late binding。就算要做初始化,也该把初始化的method设定为静态。可这里却反常地使用了一般的method。所以不得不说,这样编写代码的逻辑是否有问题。
再来就可以引述陈皓文中总结性的话了:
在语言设计的时候,“在构造函数中调用虚函数”是个两难的问题。
如果调用的是父类的函数的话,这个有点违反虚函数的定义。
如果调用的是子类的函数的话,这可能产生问题的:因为在构造子类对象的时候,首先调用父类的构造函数,而这时候如果去调用子类的函数,由于子类还没有构造完成,子类的成员尚未初始化,这么做显然是不安全的。
C++选择了第一种,而Java选择了第二种。
无论是哪一种,这种用法本身其实就是有问题的。我想,除了应付面试、考试,这样的编写方式就不应该让它存在。而不是反过来,去钻研它为什么是对的。这只不过是拿更多的错误去掩盖已成事实的错误。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/77095.html
摘要:对子类成员数据按照它们声明的顺序初始化,执行子类构造函数的其余部分。参考类的初始化顺序引了大半类加载的时机 jvm系列 垃圾回收基础 JVM的编译策略 GC的三大基础算法 GC的三大高级算法 GC策略的评价指标 JVM信息查看 GC通用日志解读 jvm的card table数据结构 Java类初始化顺序 Java对象结构及大小计算 Java的类加载机制 Java对象分配简要流程 年老...
摘要:中的继承初始化顺序父类和子类年龄动物可以吃东西类执行了年龄狗可以吃东西类执行了对象的属性和构造方法年龄动物可以吃东西类执行了的 java中的继承初始化顺序 showImg(https://segmentfault.com/img/bVbnBI1?w=1277&h=671); showImg(https://segmentfault.com/img/bVbnBKG?w=811&h=427...
摘要:底层基于拉链式的散列结构,并在中引入红黑树优化过长链表的问题。在其之上,通过维护一条双向链表,实现了散列数据结构的有序遍历。 原文地址 LinkedHashMap LinkedHashMap继承自HashMap实现了Map接口。基本实现同HashMap一样,不同之处在于LinkedHashMap保证了迭代的有序性。其内部维护了一个双向链表,解决了 HashMap不能随时保持遍历顺序和插...
摘要:在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征。 在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征。 1.再论向上转型 多态作用:消除类型之间的耦合关系. 2.转机 绑定:将一个方法调用同一个方法主体关联起来. 前期绑定:在程序执行前就进行绑定(面向过程语言默认绑定方式). 后期绑定:也叫动态绑定或运行时绑定,在运行时根据对象的类型进行绑...
摘要:也就是说,一个实例变量,在的对象初始化过程中,最多可以被初始化次。当所有必要的类都已经装载结束,开始执行方法体,并用创建对象。对子类成员数据按照它们声明的顺序初始化,执行子类构造函数的其余部分。 类的拷贝和构造 C++是默认具有拷贝语义的,对于没有拷贝运算符和拷贝构造函数的类,可以直接进行二进制拷贝,但是Java并不天生支持深拷贝,它的拷贝只是拷贝在堆上的地址,不同的变量引用的是堆上的...
摘要:初始化顺序有无父类有将父类加载进内存。如果有初始化子类对象。初始化完毕之后,再执行构造器内的方法。初始化完父类后,依次初始化子类的。 初始化顺序:1.有无父类?有:将父类加载进内存。直到将所有的父类加载完毕。再从顶层父类按照代码的顺序执行静态代码,执行完最顶层的,在执行下一层的,依次类推,直到执行完所有的静态代码。(1)如果有初始化子类对象。那么同样会先调用父类的构造器,并且会先执行最...
阅读 2570·2021-11-23 09:51
阅读 3120·2019-08-30 15:54
阅读 1070·2019-08-30 14:14
阅读 3542·2019-08-30 13:59
阅读 1393·2019-08-29 17:09
阅读 1468·2019-08-29 16:24
阅读 2848·2019-08-29 15:43
阅读 911·2019-08-29 12:45