重拾Java

最近交付了一个Android项目,用的是JDK7,回味了一把自己的起家手艺。项目做完了,整个过程中,我对Android开发的感触此起彼。一开始略表失望(因为一个Android开发者亲自告诉我他做Android从来不写测试),中间因为自己缺乏经验,踩了很多坑,对代码进行了多次的换血般的重构,可谓心累,不过成长也非常大,后慢慢熟悉了Android,参加了公司的面试官培训,觉得自己有必要把Android做的好一点点(包括代码结构设计,测试覆盖率)。

重拾起Java后,突然看到之前写了一篇关于Java面向对象初始化的文章,最初这篇文章是源于一道阿里巴巴的面试题,此时我再次把它整理出来,作为对Java的怀念。

文章很简短,建议放慢脚步多思考,并且能够亲自在自己的机器上运行,分析输出结果。

代码镜头

因为源于一道面试题,变量命名都很随意,旨在说明原理,在实际编写代码的时候要坚决杜绝这种命名方式。

public class InitializeDemo {
	private static int k = 1;
	private static InitializeDemo t1 = new InitializeDemo("t1");
	private static InitializeDemo t2 = new InitializeDemo("t2");
	private static int i = print("i");
	private static int n = 99;
	static {
		print("静态块");
	}
	private int j = print("j");
	{
		print("构造块");
	}
	public InitializeDemo(String str) {
		System.out.println((k++) + ":" + str + "   i=" + i + "    n=" + n);
		++i;
		++n;
	}
	public static int print(String str) {
		System.out.println((k++) + ":" + str + "   i=" + i + "    n=" + n);
		++n;
		return ++i;
	}
	public static void main(String args[]) {
		new InitializeDemo("init");
	}
}

运行结果

将这段代码运行后可以看到输出结果如下:

1:j   i=0    n=0
2:构造块   i=1    n=1
3:t1   i=2    n=2
4:j   i=3    n=3
5:构造块   i=4    n=4
6:t2   i=5    n=5
7:i   i=6    n=6
8:静态块   i=7    n=99
9:j   i=8    n=100
10:构造块   i=9    n=101
11:init   i=10    n=102

核心概念

对于Java对象初始化,有时候会看到一些晦涩难懂的解释说明,弄得一些初学者本来还清楚,看了之后不是那么清楚了。其实,掌握几条核心的概念,任何事情只要抓住核心的宗旨,就变得简单了。

1. 静态属性和静态代码块都是在类加载的时候初始化和执行,两者的优先级别一致,且高于非静态成员,按照编码顺序执行。
2. 非静态属性和匿名构造器在所有的构造方法之前执行,两者的优先级别一致,按照编码执行顺序。
3. 以上执行完毕后才执行构造方法中的代码。

初始化分析

那么,心中秉持着核心概念,我们来简单分析每一步做了什么,就清楚明白了。

 1. 运行main方法的时候,JVM会调用`ClassLoader`来加载InitializeDemo类,那么一切源于这次加载。
 2. 四个静态属性,按顺序逐一初始化这四个静态属性。
 3. private static int k = 1; 此时将k初始化为1。
 4. private static InitializeDemo t1 = new InitializeDemo("t1");创建InitializeDemo对象,那么按照核心理念中的顺序,先执行private int j = print("j");,打印出j,然后执行构造块,最后执行构造方法。
 5. private static InitializeDemo t2 = new InitializeDemo("t2");同步骤4。
 6. private static int i = print("i");打印i。
 7. private static int n = 99;直到这一步,n才被赋值为99,之前是从默认的0开始++的。
 8. 静态属性初始化完毕,代码走到静态块,打印出静态块,此时n=99。
 9. 静态属性和静态块执行完毕,然后执行main方法中的代码new InitializeDemo("init");
10. main方法中创建对象,先初始化非静态属性,private int j = print("j");打印j,然后执行构造块,最后执行构造方法。

只要把握住核心概念,碰到在复杂的问题也都不会乱了分寸。


总结

1. 静态只在类加载的时候执行,且执行一次。
2. 非静态只在实例化的时候执行,且每次实例化都执行。
3. 静态在非静态之前执行。
4. 静态属性和静态块的执行顺序取决于编码顺序,对它们一视同仁。
5. 非静态属性和构造块的执行顺取决于编码顺序,对它们也一视同仁。
6. 最后执行构造方法。

上面的总结有点绕对吧,问题进一步简化的话,就更好理解了

我们只需要将静态代码块视为一个静态属性,将构造块视为一个非静态属性,那么问题简化到了这种路线:

静态属性–>非静态属性–>构造方法


Posted by Yuan Shenjian • June 15th, 2016 @ ThoughtWorks®

版权声明:自由转载•非商用•非衍生•保持署名 | Creative Commons BY-NC-ND 3.0

原文链接:http://sjyuan.cc/java-object-initialization/
支持原创

⤧  Next post Java泛型•认识泛型 ⤧  Previous post Learning JavaScript 翻译笔记(二)