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

 更新时间:2016年10月04日 08:50:36   投稿:daisy  
在Java中,类装载器把一个类装入Java虚拟机中,要经过三个步骤来完成:装载、链接和初始化,网上关于Java中对象初始化顺序的文章很多,这篇文章我们将详细介绍Java中对象初始化顺序。有需要的可以参考学习。

Java技术迷

前言

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

1
2
3
4
5
6
7
8
9
package com.ds.test;
 
public class Upper {
 String upperString;
 
 public Upper() {
 Initializer.initialize(this);
 }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();
 }
}
1
2
3
4
5
6
7
8
9
10
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 这个类可以得到什么输出?在这个极简的例子中可以更容易地看到整个形势,但是这个情形发生在现实中会有非常多的代码分散一个人的注意力。

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

1
2
Upper: upperInited
Lower: null;

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();
 }
}

现在的输出是这样的:

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

总结

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

蓄力AI

微信公众号搜索 “ 脚本之家 ” ,选择关注

程序猿的那些事、送书等活动等着你

相关文章

  • springboot vue测试列表递归查询子节点下的接口功能实现

    springboot vue测试列表递归查询子节点下的接口功能实现

    这篇文章主要为大家介绍了springboot vue测试列表递归查询子节点下的接口功能实现,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-05-05
  • Java通过SSH连接路由器输入命令并读取响应的操作方法

    Java通过SSH连接路由器输入命令并读取响应的操作方法

    最近需要读取和修改华为路由器的配置,使用Java语言开发,通过SSH连接,输入命令并读取响应,接下来通过本文给大家介绍下Java通过SSH连接路由器,输入命令并读取响应,需要的朋友可以参考下
    2024-01-01
  • Java 中一个类提供一个默认对象的多种方法

    Java 中一个类提供一个默认对象的多种方法

    这篇文章主要介绍了Java 中一个类提供一个默认对象的多种方法,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-07-07
  • Java API学习教程之正则表达式详解

    Java API学习教程之正则表达式详解

    正则表达式的强大众所周知,它令程序员的头痛程度也数一数二的。下面这篇文章主要给大家介绍了关于Java API学习教程之正则表达式的相关资料,文中通过示例代码介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-07-07
  • IntelliJ IDEA中如何构建Spring Boot的项目

    IntelliJ IDEA中如何构建Spring Boot的项目

    这篇文章主要介绍了IntelliJ IDEA中如何构建Spring Boot的项目问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • 解读Jvm的内存结构与GC及jvm参数调优

    解读Jvm的内存结构与GC及jvm参数调优

    这篇文章主要介绍了解读Jvm的内存结构与GC及jvm参数调优方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-05-05
  • MyBatis框架零基础快速入门案例详解

    MyBatis框架零基础快速入门案例详解

    MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架
    2022-04-04
  • Spring boot整合shiro+jwt实现前后端分离

    Spring boot整合shiro+jwt实现前后端分离

    这篇文章主要为大家详细介绍了Spring boot整合shiro+jwt实现前后端分离,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-12-12
  • SpringBoot中的application.properties无法加载问题定位技巧

    SpringBoot中的application.properties无法加载问题定位技巧

    这篇文章主要介绍了SpringBoot中的application.properties无法加载问题定位技巧,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • JAVA如何按字节截取字符串

    JAVA如何按字节截取字符串

    这篇文章主要介绍了JAVA如何按字节截取字符串,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01

最新评论