博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java对象初始化顺序
阅读量:2117 次
发布时间:2019-04-30

本文共 2522 字,大约阅读时间需要 8 分钟。

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

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

原文链接: 
 翻译: 

译文链接: 

转载地址:http://qyref.baihongyu.com/

你可能感兴趣的文章
Tomcat 7优化前及优化后的性能对比
查看>>
Java Guava中的函数式编程讲解
查看>>
Eclipse Memory Analyzer 使用技巧
查看>>
tomcat连接超时
查看>>
谈谈编程思想
查看>>
iOS MapKit导航及地理转码辅助类
查看>>
检测iOS的网络可用性并打开网络设置
查看>>
简单封装FMDB操作sqlite的模板
查看>>
iOS开发中Instruments的用法
查看>>
iOS常用宏定义
查看>>
什么是ActiveRecord
查看>>
有道词典for mac在Mac OS X 10.9不能取词
查看>>
关于“团队建设”的反思
查看>>
利用jekyll在github中搭建博客
查看>>
Windows7中IIS简单安装与配置(详细图解)
查看>>
linux基本命令
查看>>
BlockQueue 生产消费 不需要判断阻塞唤醒条件
查看>>
强引用 软引用 弱引用 虚引用
查看>>
数据类型 java转换
查看>>
"NetworkError: 400 Bad Request - http://172.16.47.117:8088/rhip/**/####t/approval?date=976
查看>>