最近我发现了一个有趣的问题,这个问题的答案乍一看下骗过了我的眼睛。看一下这三个类:

package com.ds.test;
 
public class Upper {
String upperString;
 
public Upper() {
  Initializer.initialize(this);
}
}

package com.ds.test;
 
public class Lower extends Upper {
 
String lowerString = null;
 
public Lower() {
  super();
  System.out.println("Upper:  " + upperString);
  System.out.println("Lower:  " + lowerString);
}
 
public static void main(final String[] args) {
  new Lower();
}
}

package com.ds.test;
public class Initializer {
static void initialize(final Upper anUpper) {
  if (anUpper instanceof Lower) {
   Lower lower = (Lower) anUpper;
   lower.lowerString = "lowerInited";
  }
  anUpper.upperString = "upperInited";
}
}

运行 Lower 这个类可以得到什么输出?在这个极简的例子中可以更容易地看到整个形势,但是这个情形发生在现实中会有非常多的代码分散一个人的注意力。

不管怎么样,输出是像这样的:

Upper:  upperInited
Lower:  null;

虽然小示例中使用了 String 类型,Initializer 类的实际代码中有一个用于注册的委托对象,与 Lower 类的功能是相同的 — 至少 Lower 类是这个意图。但由于某些原因在运行应用程序时没有工作。取而代之的是,使用了默认路径,委托对象没有被设置 (null)。

现在稍微改变一下 Lower 的代码:

package com.ds.test;
 
public class Lower extends Upper {
 
String lowerString;
 
public Lower() {
  super();
  System.out.println("Upper:  " + upperString);
  System.out.println("Lower:  " + lowerString);
}
 
public static void main(final String[] args) {
  new Lower();
}
}

现在的输出是这样的:

Upper:  upperInited
Lower:  lowerInited

发现代码中的区别了吗?

是的,这个 lowerString 字段不再明确地设置为空。为什么这么做会有不同。不管怎样参考类型字段(例如这里的 String )的默认值不是为空的吗?当然是空的。事实证明,虽然这种微小的变化显然不会以任何方式改变代码行为,但是却让结果变的不同。

那么,到底发生了什么?当查看初始化顺序的时候一切就变的清晰了:

1.main() 函数调用了 Lower 构造器。

2.Lower 的一个实例被准备好了。意味着所有的字段都被创建并且填充了默认值,例如,引用类型的默认值为空,布尔类型的默认值为 false 。在这个时候,任何的对字段的内联赋值都没有发生。

3.父类构造器被调用了。这是被语言的特性所强制执行的。所以在其他任何事发生之前,Upper 的构造器被调用了。

4.Upper 这个构造器运行并且指定了一个引用,指向 Initializer.initialize() 方法新创建的的实例。

5.Initializer 类为两个字段( upperString 和 lowerString )附上新字符串。通过使用有点肮脏的 instanceof 实例检查做到为那两个字段赋值 – 这不是一个特别好的设计模式,但是也有可行的,不用管那么多。一旦发生了,upperString 和 lowerString 的引用都不再为空。

6.Initializer.initialize() 的调用完成,Upper 构造器也同样完成。

7.现在变得有趣了:Lower 实例的构造在继续。假设在 lowerString 字段的声明中没有明确地 =null 赋值,Lower 构造器恢复执行并且打印出两个连接到字段的字符串。

然而,如果有一个明确地赋值 null 的操作,执行流程会略有不同:当父类构造器完成后,在其余的构造器运行前,任何变量初始化都会执行(参见java语言规范12.5节)。在这种情况下,之前赋值给 lowerString 的字符串引用会再一次被赋予 null 。然后继续执行其余的函数构造,现在打印 lowerString 的值为: null 。

这是一个很好的例子,不仅方便我们如何注意一些创建对象的细节(或者知道去哪里查看 Java 编码规范,打印的或者在线的),还显示了为什么像这样写初始化是很糟糕的。我们一点都不应该关心 Upper 的子类。相反的,如果因为一些原因对某些字段的初始化不能在子类本身被完成,它将只需要它自己的某些初始化帮助类的变体。在这种情况下,如果你使用 String lowString 或者 String lowerString = null 是真的没有任何区别的,它应该是什么就会是什么。

Java对象初始化顺序的更多相关文章

  1. Java变量、Java对象初始化顺序

    局部变量与成员变量: 局部变量分为: 行参:在方法签名中定义的局部变量,随方法的结束而凋亡. 方法内的局部变量:必须在方法内对其显示初始化,从初始化后开始生效,随方法的结束而凋亡. 代码块内的局部变量 ...

  2. 一文理解java对象初始化顺序

    例子 ​ Talk is cheap, Show you the code! public class ParentClass { static int parentStaticField = 1; ...

  3. java对象初始化顺序的简单验证

    以下这段小程序对调用对象构造函数时,父类构造函数.成员变量初始化函数,以及非静态初始化块调用顺序进行验证,不考虑静态成员及静态初始化块. public class Derive extends Bas ...

  4. Java提高篇——静态代码块、构造代码块、构造函数以及Java类初始化顺序

    静态代码块:用staitc声明,jvm加载类时执行,仅执行一次构造代码块:类中直接用{}定义,每一次创建对象时执行.执行顺序优先级:静态块,main(),构造块,构造方法. 构造函数 public H ...

  5. java 继承 初始化顺序

    面向对象三大特性: 封装,继承,多态 java 继承初始化顺序 先初始化父类对象, 在父类对象中先初始化父类属性,再初始化父类的构造方法,然后初始化子类对象,初始化子类对象的属性,初始化子类的构造方法 ...

  6. java继承 初始化顺序

    java继承 初始化顺序 标签: 初始化顺序 2013-08-01 10:13 879人阅读 评论(0) 收藏 举报  分类: java(8)  版权声明:本文为博主原创文章,未经博主允许不得转载. ...

  7. JAVA的初始化顺序:

    JAVA的初始化顺序: 父类的静态成员初始化>父类的静态代码块>子类的静态成员初始化>子类的静态代码块>父类的代码块>父类的构造方法>子类的代码块>子类的构造 ...

  8. 【知识总结】Java类初始化顺序说明

    微信公众号:努力编程的小猪如有问题或建议,请公众号留言 Java类初始化顺序说明 一个类中包含如下几类东西,他们前后是有顺序关系的 静态属性:static 开头定义的属性 静态方法块: static ...

  9. java 对象初始化和代码块初始化顺序

    class A { public A(){ System.out.println("测试!!!!!!!!!!!"); } } class Demo19 extends A { { ...

随机推荐

  1. NativeInt

    NativeInt ni = 100; //Wrong. Will issue an Error NativeInt ni = NativeInt(100); //Correct System::Ge ...

  2. 两年内从零到每月十亿 PV 的发展来谈 Pinterest 的架构设计(转)

    原文:Scaling Pinterest - From 0 To 10s Of Billions Of Page Views A Month In Two Years 译文:两年内从零到每月十亿 PV ...

  3. <转>linux进程间通信<一>

    这篇文章真心不错,只是代码比较久,有些地方需求大家自行修改.先全文转载,以备复习只用.原文链接为:http://www.ibm.com/developerworks/cn/linux/l-ipc/pa ...

  4. Jsp页面设计易忘点

    文本标签: <b>文本加粗 <i>斜体 <u>下划线 <sub>作为下标 <sup>作为上标 样式: italic;斜体 text-deco ...

  5. hdu 4815 Little Tiger vs. Deep Monkey(01背包)

    http://acm.hdu.edu.cn/showproblem.php?pid=4815 Description A crowd of little animals is visiting a m ...

  6. KMP应用http://acm.hdu.edu.cn/showproblem.php?pid=2594

    riemann与marjorie拼接后riemannmarjorie前缀与后缀公共部分为 rie 长度为 3(即next[l] = next[14]的值,l为拼接后的长度)但:aaaa与aa拼接后aa ...

  7. redis缓存数据表

    直观上看,数据库中的数据都是按表存储的:更微观地看,这些表都是按行存储的.每执行一 次select查询,数据库都会返回一个结果集,这个结果集由若干行组成.所以,一个自然而然 的想法就是在Redis中找 ...

  8. POJ3468 A Simple Problem with Integers(线段树延时标记)

    题目地址http://poj.org/problem?id=3468 题目大意很简单,有两个操作,一个 Q a, b 查询区间[a, b]的和 C a, b, c让区间[a, b] 的每一个数+c 第 ...

  9. HDU 4496 D-City (并查集)

    题意:给定一个图,问你每次删除一条边后有几个连通块. 析:水题,就是并查集的运用,倒着推. 代码如下: #include <cstdio> #include <string> ...

  10. angular select中ng-options使用

    function selectCtrl($scope) { $scope.selected = ''; $scope.model = [{ id: 10001, mainCategory: '男', ...