JAVA: 理解Java中的类初始化

在运行 Java 代码时,很多时候需要弄清楚程序执行的流程,而面向对象的 Java 程序并非像主要为面向过程而设计的 C 语言一样去顺序执行(简单按照代码的顺序),这使得对于类文件的加载以及执行流程的理解非常重要。本文简单介绍了 Java 类的初始化部分具体过程,包括成员变量、静态代码块、构造函数等的初始化时机及执行流程。

初始化时机

根据 javase 8 的文档说明[1],一个类(本文暂不考虑接口)T 将在下列情况第一次出现前立即被初始化:

  • T 的一个实例被创建[2]
  • T 的一个静态方法被调用;
  • T 声明的一个静态变量被赋值;
  • T 声明的一个静态变量被使用并且这个变量不是常量[3]
  • 暂不考虑这种情况(涉及到顶层类,断言,内部类等)

另外要注意:当一个类初始化时,它的父类如果没有初始化则会先被初始化。

初始化步骤

首先要弄清楚几个基本概念:静态代码块、构造代码块、构造方法、成员变量、子父类的初始化。

概念分析

  • 静态代码块, 一般用于类的数据初始化,形式为:
    class StaticDemo{
    	static {
    		int a=1;
    		System.out.println("I am a static block!");
    	}
    }
  • 构造代码块,与静态代码块的区别在于少了 static 关键字:
    class ConstructorBlockDemo{
    	{
    		int a=1;
    		System.out.println("I am a constructor block!");
    	}
    }
  • 构造方法
    public class ConstructorDemo{
    	public ConstructorDemo(){
    		int a=1;
    		System.out.println("I am a constructor!");
    	}
    }
  • 成员变量,一般就是类所定义的变量(描述类的属性),这个容易理解。分为默认初始化和显示初始化:
    class FieldDemo{
    	int b;//initialized implicitly
    	int a=1;//initialized explicitly	
    }

需要注意的是,a=1;在有些情况下(比如 FieldDemo 还有父类)并不一定立即在变量 a 分配内存后被赋值。

  • 子父类的初始化,一般先进行父类初始化,然后进行子类初始化,分层进行。

执行顺序

【update】本文提到的程序可以使用在线可视化 IDE 帮助理解,Java Tutor – Visualize Java code execution to learn Java online

1. 首先程序运行时,从 main 方法所在的主类开始,但并 不意味着就是从 main 方法开始。而是 JVM 开始加载类:

public class LoadClassDemo{
	static {
		int a=1;
		System.out.println("I am a static block!");
	}
	
	public static void main(String[] args) {
		System.out.println("I am the main method!");
	}
}

上述程序输出结果为(可以先自己考虑下,然后点击图片放大核对下):

JAVA: 理解Java中的类初始化
JAVA: 理解Java中的类初始化

JVM 在加载 LoadClassDemo 时,静态代码块就开始执行了,从而对类的信息初始化。并且静态代码块只在类加载时执行,因此只执行一次。

2. 构造代码块。静态代码块执行完毕后,构造代码块就开始执行了,而且每次生成类的实例都会执行,区别于静态代码块只执行一次。比如:

class ObjectDemo {
	static {
		System.out.println("ObjectDemo static block!");
	}
	
	{
		System.out.println("ObjectDemo constructor block");
	}
	
	public ObjectDemo() {
		System.out.println("ObjectDemo constructor");
	}
}
public class ConstructorBlockTest {
	static {
		System.out.println("I am a static block!");
	}
	
	public static void main(String[] args) {
		System.out.println("I am the main method!");
		
		ObjectDemo od1 = new ObjectDemo();
		ObjectDemo od2 = new ObjectDemo();
	}
}

上述程序输出结果为(可以先自己考虑下,然后点击图片放大核对下):

JAVA: 理解Java中的类初始化
JAVA: 理解Java中的类初始化

3. 成员变量,在创建实例时被分配内存,之后进行默认初始化和显示初始化(如有)。

4. 构造方法,上述过程之后构造方法才被调用生成对象。

class ObjectDemo {
	static {
		System.out.println("ObjectDemo static block!");
	}
	
	{
		System.out.println("ObjectDemo constructor block");
	}
	String f="field value";
	public ObjectDemo() {
		System.out.println("ObjectDemo constructor");
	}
}
class ObjectDemo2 {
	static {
		System.out.println("ObjectDemo static block!");
	}
	
	{
		System.out.println("ObjectDemo constructor block");
	}
	String f2="field value";
	public ObjectDemo2() {
		System.out.println("ObjectDemo constructor");
		f2="field value changed!";
	}
}
public class InitializationDemo {
	static {
		System.out.println("I am a static block!");
	}
	
	public static void main(String[] args) {
		System.out.println("I am the main method!");
		
		ObjectDemo od1 = new ObjectDemo();
		ObjectDemo od2 = new ObjectDemo();
		System.out.println(od1.f);
		ObjectDemo2 od3 = new ObjectDemo2();
		System.out.println(od3.f2);
	}
}

仔细分析上述综合示例结果,(然后点击图片放大核对):

JAVA: 理解Java中的类初始化
JAVA: 理解Java中的类初始化

实例理解

实例 1

请分析一下代码执行结果,然后思考注释掉的 super 与结果又何关系

class X {
	static String xVar="x value";
	Y b = new Y();
	static{
		System.out.println(xVar);
		System.out.println("X static block");
		xVar="static value";
	}
	X() {		
		System.out.println("X");
		System.out.println(xVar);
		xVar="x value changed!";
	}
}
class Y {
	String yVar="Y value";
	Y() {
		System.out.println("Y");
		System.out.println(yVar);
		yVar="y value changed!";
	}
	void show(){
		System.out.println(yVar);
	}
}
public class Z extends X {
	Y y ;
	static{
		System.out.println("Z static block");
	}
	{
		y = new Y();
		y.show();
	}
	Z() {
		//super
		System.out.println("Z");
	}
	public static void main(String[] args) {
		System.out.println(new Z().xVar);
	}
}

分析 1

先给出结果(点击图片放大):

JAVA: 理解Java中的类初始化
JAVA: 理解Java中的类初始化

分析过程如下(注意子父类的分层初始化):
1. 首先找到 main 方法所在类为 Z,所以最先开始加载 Z 类,由于 Z 类还有父类 X,因此又加载 X 类,先对 X 类初始化;
2.X 类加载并为静态变量 xVar 初始化,接着执行静态代码块,输出 x value,接着输出X static block 并改变 xVar 值;
3.X 类加载完之后,Z 类开始执行静态代码块输出 Z static block
4. 接着初始化 X 类,初始化成员变量 y,因此输出Yy value,由于没有构造代码块,所以继续执行构造方法输出 Xstatic value
5. 到此 X 类初始化完成,再接着执行 Z 类的构造代码块输出 YY value并改变 yVar 指,执行 show()方法后输出y value changed!,最后执行 Z 的构造方法,输出Z
6. 到此 Z 类初始化完成,最终输出x value changed![4]

实例 2

还有一个比较典型的例子[5],请分析程序执行结果:

class Base
{
    Base()
    {
        preProcess();
    }
    
    void preProcess()
    {
    }
}
class Derived extends Base
{
    public String whenAmISet = "set when declared";
    
    public Derived()
    {
        whenAmISet = "set in constructor";
    }
    
    void preProcess()
    {
        whenAmISet = "set in preProcess()";
    }
}
public class TestInitialization
{
    public static void main(String[] args)
    {
        Derived d = new Derived();
        System.out.println(d.whenAmISet);
    }
}

分析 2

详见脚注 3,要注意本文中的例子在 Derived 类中添加了构造方法,所以最终结果为 set in constructor
更新:eclipse 调试演示图

后记

这次关于 Java 中类的初始化就介绍到这里了,关于 Java 类的生命周期可以参看 [6] 其实还有关于接口并没有提到,有兴趣可以参考 JLS 相关内容,即脚注 1 所引。另外,还是谈谈如何去深入学习一些知识的方法,比如这次类的初始化,首先当然是 search 关键字,然后自己动手写代码观察思考(还可以使用 Eclipse 断点观察程序执行流程),接着很有必要参考 JLS 中关于 Java Execution 的部分(Chapter 12),最后要想深入理解就得自己多改写代码,思考如何才能理解为何是这样执行的、这样设计有何好处。

 

原文:https://www.caoqq.net/java-class-initialization.html

本地:JAVA: 理解Java中的类初始化

Loading

Add a Comment

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload CAPTCHA.