Think in Java之构造器的真正调用顺序
构造器是OOP的重要组成部分,很多人认为它很容易。只不过是new了一个对象而已。而think in java的作者却告诉我们,其实这并不容易。先看下面这个例子。在你没看结果之前,你觉得你的答案是对的么。
构造器是OOP的重要组成部分,很多人认为它很容易。只不过是new了一个对象而已。而think in java的作者却告诉我们,其实这并不容易。
先看下面这个例子。在你没看结果之前,你觉得你的答案是对的么。
- package com.tudou.t1;
- class Meal {
- Meal() {
- System.out.println("meal");
- }
- }
- class Bread {
- Bread() {
- System.out.println("Bread");
- }
- }
- class Cheese {
- Cheese() {
- System.out.println("Cheese");
- }
- }
- class Lettuce {
- Lettuce() {
- System.out.println("Lettuce");
- }
- }
- class Lunch extends Meal{
- Lunch() {
- System.out.println("Lunch");
- }
- }
- class PortableLunch extends Lunch{
- PortableLunch() {
- System.out.println("PortableLunch");
- }
- }
- public class Sandwich extends PortableLunch {
- private Bread b = new Bread();
- private Cheese c = new Cheese();
- private Lettuce l = new Lettuce();
- public Sandwich() {
- System.out.println("Sandwich");
- }
- public static void main(String[] args) {
- new Sandwich();
- }
- }
控制台的打印结果为:
meal
Lunch
PortableLunch
Bread
Cheese
Lettuce
Sandwich
复杂对象调用构造器的顺序应该遵循下面的原则:
1、调用基类[即父类]构造器。这个步骤会不断反复递归下去,首先是构造器这种层次结构的根,然后是下一层导出类[即子类],等等。直到最底层的导出类。[从最上层的meal一直递归到PortableLunch]
2、按声明顺序调用成员的初始化方法。[即上面的Bread,Cheese,Lettuce]
3、调用导出类构造器的主体[即Sandwich]
可见,调用类本身是最后完成初始化的,最先完成初始化的是最顶级的基类,所谓没有父亲,哪来的儿子。处于它们中间的是调用类本身拥有的子对象。因为你不可能在子对象初始化之前用本类调用它,所以它一定在本类调用之前,父类调用之后完成初始化的。
那么这个说法是不是一定成立呢。结果是否定的。你必须知道JVM的编绎原理才可能知道,它究竟是如何工作的。
我们来看下面这个例子,来解释为什么它不一定。因为在继承和重写的时候,这种情况变得有点诡异。
深入探究:
- package com.tudou.t1;
- public class ConstrcutorTest2 {
- public static void main(String[] args) {
- new RoundGlyph(5);
- }
- }
- class Glyph {
- void draw() {
- System.out.println("Glyph draw()");
- }
- Glyph() {
- System.out.println("Glyph before draw();");
- draw();
- System.out.println("Glyph after draw();");
- }
- }
- class RoundGlyph extends Glyph {
- private int radius = 1;
- RoundGlyph(int r) {
- radius = r;
- System.out.println("RoundGlyph(),radius:" + radius);
- }
- void draw() {
- System.out.println("RoundGlyph.draw(),radius:" + radius);//此处打印是0,而不是1
- }
- }
控制台打印结果:
Glyph before draw();
RoundGlyph.draw(),radius:0
Glyph after draw();
RoundGlyph(),radius:5
为什么RoundGlyph.draw(),radius:0这里会是0呢。
默认的1哪去了?值自己会变么。其实上面的讲述并不完整。,而这正是解决谜题的关键所在。初始化的实际过程之前,实际在还有一步。
0:在其他任何事物发生之前,将分配对象的存舍得空间初始化为二进制的零。
而它后面的初始化顺序就是上面的3步。
- 调用基类[即父类]构造器。这个步骤会不断反复递归下去,首先是构造器这种层次结构的根,然后是下一层导出类[即子类],等等。直到最底层的导出类。
- 按声明顺序调用成员的初始化方法。
- 调用导出类构造器的主体
也就是说,实际上有4步,知道这些你对对象初始化构造器才可能有个清楚的认识。
JAVA有更多的精髓等着人们去挖掘,而不仅仅是知道如何去使用它。
因为你不知道什么时候它会出现意想不到的后果,而这个错误,可能你根本就想不出来。
编写构造器时有一条准则:
用尽可能简单的方法使对象进入正常状态,如果可以的话,避免调用其它方法。
在构造器内唯一能够安全调用的那些方法是基类中的final或者private方法,这些方法不能被覆盖,因此也就不会出现令人惊讶的问题。
你可能无法总是遵循这条准则,但是应该朝着它努力。
学任何语言,请打好基础,它是你以后扩展的人生基石。
Think in Java之构造器的真正调用顺序的更多相关文章
- Java类加载及实例化的调用顺序
标题起得略拗口,大概意思就是说在一个Java类中,域和构造方法的调用顺序. 1. 没有继承的情况 单独一个类的场景下,初始化顺序为依次为 静态数据,继承的基类的构造函数,成员变量,被调用的构造函数. ...
- Java父类与子类方法调用顺序
父类 FatherClass package 父类与子类方法调用顺序; /** * 父类 * @author shundong * */ public class FatherClass { priv ...
- java 类加载及实例化的调用顺序
1.没有继承的情况 单独一个类的场景下,初始化顺序为依次为 静态变量和静态代码块(看两者的书写顺序),继承的基类的构造函数,成员变量,被调用的构造函数. 代码呈现: public class Test ...
- java实例变量及方法调用顺序
public class Base { private String name="base"; public Base(){ sayHello(); } void sayHello ...
- Java构造器:级联调用,调用兄弟构造器
级联调用: class Father{ Father(){ System.out.println("Father birth"); } public void announce() ...
- Java构造器的调用顺序
<Java编程思想>中对构造器的调用顺序有如下描述: “构造器实际上是static方法,只不过该static声明是隐式的.” “基类的构造器总是在导出类的构造过程中被调用,而且按照继承层次 ...
- java中构造器的调用顺序
在编程的过程中,我们经常会遇到多个类的继承问题,那么多个类的构造器是按照什么顺序调用的呢? 先看一段代码: public class Meal { public Meal() { System.out ...
- Java私有构造器
Java私有构造器:使用private关键字声明的构造函数.由于类的构造函数时私有的,所以此类不能被实例化,同时也不能被继承.<Effective Java>第三条:用私有构造器或者枚举强 ...
- java 父类构造器
当创建任何java对象时,程序总会首先调用系统的父类非静态初始化块(隐式执行)和父类构造器(从object开始(java程序中所有类的最终父类都是java.lang.Object类,使用语句super ...
- Java :构造器中的显式参数和this隐式参数
1.构造器 写一个Java类,首先要先从构造器开始,构造器与类同名,在构造类的对象时会先从构造器开始. 构造器总是伴随着new操作符的执行而被调用. 构造器主要是用来初始化类的实例域. 构造器的特点: ...
随机推荐
- uniapp中,getApp()返回的实例到底是什么?为什么getApp()返回的实例无法访问vuex的$store
按uniapp官方手册中说,getApp()函数用于获取当前应用实例.当前应用,也就是说当前应用程序.因为getApp()返回的实例可以用于访问app.vue中的globaldata,因此这个当前应用 ...
- Python 潮流周刊#67:uv 的重磅更新(摘要)
本周刊由 Python猫 出品,精心筛选国内外的 250+ 信息源,为你挑选最值得分享的文章.教程.开源项目.软件工具.播客和视频.热门话题等内容.愿景:帮助所有读者精进 Python 技术,并增长职 ...
- SQL Server – 基本操作 Table 和 Column
前言 日常都是用 EF Core 来管理 Database, 偶尔也用 Management Studio, 就是很少手写 Command. 虽然网上一拉就有很多, 但是每次写单侧都要到处找还是挺烦的 ...
- ASP.NET Core – Razor Pages 冷知识
Multiple Form Binding 问题 在一个 page 里面有 2 张 form, 那么就会有 2 个 model binding. 当任何一个 submit 的时候. 由于 2 个 mo ...
- OData – Routing
前言 以前我都是把 ODataController 和普通 API Controller 分开做. (因为 OData 实在多 Bug, 好东西尽量不要掺和它) Read 的部分用 OData, CU ...
- cortex-m3 m4 异常机制
文章写的很好,待整理 1.[STM32]HardFault问题详细分析及调试笔记 https://blog.csdn.net/m0_54916619/article/details/129979222 ...
- 系统编程-进程-先后fork或open一个文件的区别
关联博文: 当文件操作遇上fork Linux内核的文件结构体 struct file { ......... struct path f_path; //文件的路径 #define f_dentry ...
- SuperMap iServer新增支持FlatGeobuf数据格式,查询渲染性能提升2-3倍
导语 FlatGeobuf是一种地理数据存储格式,采用了二进制编码,相比其他文本或XML格式更高效,可以显著减小文件大小,这使得数据的传输和存储更加快速和高效. SuperMap iServer 11 ...
- template<> 模板特化
template<> 是用于 模板特化(Template Specialization) 的一种语法. 模板特化允许你为某些特定的模板参数提供不同的实现.例如,template<&g ...
- 6. CSS有哪些方法可以提升层级
1. 使用 z-index 2. 使用定位,脱离标准流