文档章节

Java对象初始化顺序

火力全開
 火力全開
发布于 2016/10/06 22:20
字数 1070
阅读 4
收藏 0
点赞 0
评论 0

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

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

本文转载自:http://www.importnew.com/21832.html

火力全開
粉丝 19
博文 194
码字总数 17847
作品 0
卢湾
高级程序员
两道面试题,带你解析Java类加载机制

文章首发于【博客园-陈树义】,点击跳转到原文《两道面试题,带你解析Java类加载机制》 在许多Java面试中,我们经常会看到关于Java类加载机制的考察,例如下面这道题: 请写出最后的输出字符...

陈树义 ⋅ 06/12 ⋅ 0

两道面试题带你解析 Java 类加载机制

在许多Java面试中,我们经常会看到关于Java类加载机制的考察,例如下面这道题: class Grandpa{ } class Father extends Grandpa{ }class Son extends Father{ }public class Initialization...

⋅ 06/13 ⋅ 0

Java和Android ClassLoder对比以及Class加载过程

Java的ClassLoder的父子关系如下:Bootstrap--ExtClassClassLoader---AppClassLoader。 JVM启动时先运行启动类加载器Bottstrap,主要加载Java核心API;然后加载扩展类加载器ExtClassLoder,该...

JasmineBen ⋅ 05/23 ⋅ 0

14、Java并发性和多线程-Java ThreadLocal

以下内容转自http://ifeve.com/java-theadlocal/: Java中的ThreadLocal类可以让你创建的变量只被同一个线程进行读和写操作。因此,尽管有两个线程同时执行一段相同的代码,而且这段代码又有...

easonjim ⋅ 2017/06/16 ⋅ 0

Java 面试知识点解析(三)——JVM篇

前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大部...

我没有三颗心脏 ⋅ 05/16 ⋅ 0

Android JNI学习(四)——JNI的常用方法的中文API

本系列文章如下: Android JNI(一)——NDK与JNI基础 Android JNI学习(二)——实战JNI之“hello world” Android JNI学习(三)——Java与Native相互调用 Android JNI学习(四)——JNI的常用方法...

隔壁老李头 ⋅ 05/09 ⋅ 0

Java提高——对象与内存控制

实例变量和类变量 Java内存管理分为两个方面:内存分配和内存回收。 内存分配是特指创建Java对象时,JVM为该对象在堆内存中所分配的内存空间 内存回收是指当Java对象失去引用,变成垃圾时,J...

qq_30604989 ⋅ 04/18 ⋅ 0

从 Java 到 Kotlin,再从 Kotlin 回归 Java

由于此博客文章引起高度关注和争议,我们认为值得在Allegro上增加一些关于我们如何工作和做出决策的背景。Allegro拥有超过50个开发团队可以自由选择被我们PaaS所支持的技术。我们主要使用Jav...

oschina ⋅ 05/31 ⋅ 0

Java内存模型之happens-before和重排序

重排序 在执行程序时,为了提供性能,处理器和编译器常常会对指令进行重排序,满足以下两个条件: 在单线程环境下不能改变程序运行的结果; 存在数据依赖关系的不允许重排序 happens - befo...

细节探索者 ⋅ 05/02 ⋅ 0

类加载器ClassLoader-1

一, 类加载器深入剖析 1,Java虚拟机与程序的生命周期 在如下几种情况下,Java虚拟机将结束生命周期: –执行了System.exit()方法 –程序正常执行结束 –程序在执行过程中遇到了异常或错误而...

康熙兄弟 ⋅ 06/02 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

从零开始搭建Risc-v Rocket环境---(1)

为了搭建Rocke环境,我买了一个2T的移动硬盘,安装的ubuntu-16.04 LTS版。没有java8,gcc是5.4.0 joe@joe-Inspiron-7460:~$ java -version程序 'java' 已包含在下列软件包中: * default-...

whoisliang ⋅ 28分钟前 ⋅ 0

大数据学习路线(自己制定的,从零开始学习大数据)

大数据已经火了很久了,一直想了解它学习它结果没时间,过年后终于有时间了,了解了一些资料,结合我自己的情况,初步整理了一个学习路线,有问题的希望大神指点。 学习路线 Linux(shell,高并...

董黎明 ⋅ 34分钟前 ⋅ 0

systemd编写服务

一、开机启动 对于那些支持 Systemd 的软件,安装的时候,会自动在/usr/lib/systemd/system目录添加一个配置文件。 如果你想让该软件开机启动,就执行下面的命令(以httpd.service为例)。 ...

勇敢的飞石 ⋅ 36分钟前 ⋅ 0

mysql 基本sql

CREATE TABLE `BBB_build_info` ( `community_id` varchar(50) NOT NULL COMMENT '小区ID', `layer` int(11) NOT NULL COMMENT '地址层数', `id` int(11) NOT NULL COMMENT '地址id', `full_......

zaolonglei ⋅ 45分钟前 ⋅ 0

安装chrome的vue插件

参看文档:https://www.cnblogs.com/yulingjia/p/7904138.html

xiaoge2016 ⋅ 47分钟前 ⋅ 0

用SQL命令查看Mysql数据库大小

要想知道每个数据库的大小的话,步骤如下: 1、进入information_schema 数据库(存放了其他的数据库的信息) use information_schema; 2、查询所有数据的大小: select concat(round(sum(da...

源哥L ⋅ 今天 ⋅ 0

两个小实验简单介绍@Scope("prototype")

实验一 首先有如下代码(其中@RestController的作用相当于@Controller+@Responsebody,可忽略) @RestController//@Scope("prototype")public class TestController { @RequestMap...

kalnkaya ⋅ 今天 ⋅ 0

php-fpm的pool&php-fpm慢执行日志&open_basedir&php-fpm进程管理

12.21 php-fpm的pool pool是PHP-fpm的资源池,如果多个站点共用一个pool,则可能造成资源池中的资源耗尽,最终访问网站时出现502。 为了解决上述问题,我们可以配置多个pool,不同的站点使用...

影夜Linux ⋅ 今天 ⋅ 0

微服务 WildFly Swarm 管理

Expose Application Metrics and Information 要公开关于我们的微服务的有用信息,我们需要做的就是将监视器模块添加到我们的pom.xml中: 这将使在管理和监视功能得到实现。从监控角度来看,...

woshixin ⋅ 今天 ⋅ 0

java连接 mongo伪集群部署遇到的坑

部署mongo伪集群 #创建mongo数据存放文件地址mkdir -p /usr/local/config1/datamkdir -p /usr/local/config2/data mkdir -p /usr/local/config3/data mkdir -p /usr/local/config1/l......

努力爬坑人 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部