Java中对象初始化顺序的详细介绍

前言

在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的。最近我发现了一个有趣的问题,这个问题的答案乍一看下骗过了我的眼睛。看一下这三个类:

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 是真的没有任何区别的,它应该是什么就会是什么。

总结

以上就是这篇文章的全部内容了,希望这篇文章的内容对大家的学习或者工作能带来一定的帮助,如果有问题大家可以留言交流。

(0)

相关推荐

  • JavaWeb中web.xml初始化加载顺序详解

    需求说明 做项目时,为了省事,起初把初始化的配置都放在每个类中 static加载,初始化配置一多,就想把它给整理一下,这里使用servlet中的init方法初始化. web.xml说明 首先了解下web.xml中元素的加载顺序: 启动web项目后,web容器首先回去找web.xml文件,读取这个文件 容器会创建一个 ServletContext ( servlet 上下文),整个 web 项目的所有部分都将共享这个上下文 容器将 转换为键值对,并交给 servletContext 容器创建 中的

  • Java中static静态变量的初始化完全解析

    静态变量初始化顺序 1.简单规则 首先先看一段最普遍的JAVA代码: public class Test { public static Test1 t = new Test1(); public static int a = 0; public static int b; public static void main(String[] arg) { System.out.println(Test.a); System.out.println(Test.b); } } class Test1

  • 实例解析Java中的构造器初始化

    1.初始化顺序 当Java创建一个对象时,系统先为该对象的所有实例属性分配内存(前提是该类已经被加载过了),接着程序开始对这些实例属性执行初始化,其初始化顺序是:先执行初始化块或声明属性时制定的初始值,再执行构造器里制定的初始值. 在类的内部,变量定义的先后顺序决定了初始化的顺序,即时变量散布于方法定义之间,它们仍就会在任何方法(包括构造器)被调用之前得到初始化. class Window { Window(int maker) { System.out.println("Window(&quo

  • Java 数组声明、创建、初始化详解

    一维数组的声明方式: type var[]; 或type[] var; 声明数组时不能指定其长度(数组中元素的个数), Java中使用关键字new创建数组对象,格式为: 数组名 = new 数组元素的类型 [数组元素的个数] 实例: TestNew.java: 程序代码: public class TestNew { public static void main(String args[]) { int[] s ; int i ; s = new int[5] ; for(i = 0 ; i

  • 深入介绍Java对象初始化

    前言 在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的. 自动初始化(默认值) 一个类的所有基本数据成员都会得到初始化,运行下面的例子可以查看这些默认值: class Default{ boolean t; char c; byte b; short s; int i; long l; float f; double d; public void show() { System.out.println("基本类型 初始化值\n"+ "bo

  • Java中初始化块详解及实例代码

    Java中初始化块详解 在Java中,有两种初始化块:静态初始化块和非静态初始化块. 静态初始化块:使用static定义,当类装载到系统时执行一次.若在静态初始化块中想初始化变量,那仅能初始化类变量,即static修饰的数据成员. 非静态初始化块:在每个对象生成时都会被执行一次,可以初始化类的实例变量. 非静态初始化块会在构造函数执行时,且在构造函数主体代码执行之前被运行. 括号里的是初始化块,这里面的代码在创建Java对象时执行,而且在构造器之前执行! 其实初始化块就是构造器的补充,初始化块是

  • Java中对象初始化顺序的详细介绍

    前言 在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的.最近我发现了一个有趣的问题,这个问题的答案乍一看下骗过了我的眼睛.看一下这三个类: package com.ds.test; public class Upper { String upperString; public Upper() { Initializer.initialize(this); } } package com.ds.test; public class Lower extends

  • Java中的main函数的详细介绍

    Java中的main函数的详细介绍 JAVA中的主函数是我们再熟悉不过的了,相信每个学习过JAVA语言的人都能够熟练地写出这个程序的入口函数,但对于主函数为什么这么写,其中的每个关键字分别是什么意思,可能就不是所有人都能轻松地答出来的了.我也是在学习中碰到了这个问题,通过在网上搜索资料,并加上自己的实践终于有了一点心得,不敢保留,写出来与大家分享. 主函数的一般写法如下: public static void main(String[] args){-} 下面分别解释这些关键字的作用: (1)p

  • Java中批处理框架spring batch详细介绍

    spring batch简介 spring batch是spring提供的一个数据处理框架.企业域中的许多应用程序需要批量处理才能在关键任务环境中执行业务操作. 这些业务运营包括: 无需用户交互即可最有效地处理大量信息的自动化,复杂处理. 这些操作通常包括基于时间的事件(例如月末计算,通知或通信). 在非常大的数据集中重复处理复杂业务规则的定期应用(例如,保险利益确定或费率调整). 集成从内部和外部系统接收的信息,这些信息通常需要以事务方式格式化,验证和处理到记录系统中. 批处理用于每天为企业处

  • Java中的同步与异步详细介绍

    进程同步用来实现程序并发执行时候的可再现性. 一.进程同步及异步的概念 1.进程同步:就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回.也就是必须一件一件事做,等前一件做完了才能做下一件事.就像早上起床后,先洗涮,然后才能吃饭,不能在洗涮没有完成时,就开始吃饭.按照这个定义,其实绝大多数函数都是同步调用(例如sin,isdigit等).但是一般而言,我们在说同步.异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务.最常见的例子就是 sendmessage.该函数发送一个消

  • Java中的锁分类的详细介绍

    在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容如下: 公平锁/非公平锁 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级锁/重量级锁 自旋锁 上面是很多锁的名词,这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计,下面总结的内容是对每个锁的名词进行一定的解释. 公平锁/非公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁. 非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的

  • Java中的静态绑定和动态绑定详细介绍

    一个Java程序的执行要经过编译和执行(解释)这两个步骤,同时Java又是面向对象的编程语言.当子类和父类存在同一个方法,子类重写了父类的方法,程序在运行时调用方法是调用父类的方法还是子类的重写方法呢,这应该是我们在初学Java时遇到的问题.这里首先我们将确定这种调用何种方法实现或者变量的操作叫做绑定. 在Java中存在两种绑定方式,一种为静态绑定,又称作早期绑定.另一种就是动态绑定,亦称为后期绑定. 区别对比 1.静态绑定发生在编译时期,动态绑定发生在运行时 2.使用private或stati

  • Java中GC的工作原理详细介绍

    Java中GC的工作原理 引子:面试时被问到垃圾回收机制,只是粗略的讲'程序员不能直接对内存操作,jvm负责对已经超过作用域的对象回收处理',面官表情呆滞,也就没再继续深入. 转文: 一个优秀的Java程序员必须了解GC的工作原理.如何优化GC的性能.如何与GC进行有限的交互,有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等,只有全面提升内存的管理效率,才能提高整个应用程序的性能.本文将从GC的工作原理.GC的几个关键问题进行探讨,最后提出一些Java程序设计建议,如何从GC角度提高Ja

  • java 中多线程生产者消费者问题详细介绍

    java 中多线程生产者消费者问题 前言: 一般面试喜欢问些线程的问题,较基础的问题无非就是死锁,生产者消费者问题,线程同步等等,在前面的文章有写过死锁,这里就说下多生产多消费的问题了 import java.util.concurrent.locks.*; class BoundedBuffer { final Lock lock = new ReentrantLock();//对象锁 final Condition notFull = lock.newCondition(); //生产者监视

  • Java中的字符串常量池详细介绍

    Java中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new String("droid");,这两种方式我们在代码编写时都经常使用,尤其是字面量的方式.然而这两种实现其实存在着一些性能和内存占用的差别.这一切都是源于JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池. 工作原理 当代码中出现字

  • JAVA中JVM的重排序详细介绍

    在并发程序中,程序员会特别关注不同进程或线程之间的数据同步,特别是多个线程同时修改同一变量时,必须采取可靠的同步或其它措施保障数据被正确地修改,这里的一条重要原则是:不要假设指令执行的顺序,你无法预知不同线程之间的指令会以何种顺序执行. 但是在单线程程序中,通常我们容易假设指令是顺序执行的,否则可以想象程序会发生什么可怕的变化.理想的模型是:各种指令执行的顺序是唯一且有序的,这个顺序就是它们被编写在代码中的顺序,与处理器或其它因素无关,这种模型被称作顺序一致性模型,也是基于冯·诺依曼体系的模型.

随机推荐