前言
关于Java类的成员初始化顺序,一直是笔试中常见的基础考题,今天就执行顺序写一篇博文
主要从:构造函数,构造代码块,静态代码块,继承关系分析
构造函数
public A(){
//构造函数 to do something
}
对象新建时,调用对应的构造函数
构造函数的作用是用于给对象进行初始化
一个对象建立,构造函数只运行一次,而一般方法可以被该对象调用多次
构造代码块
{
//构造代码块 to do something
}
public class InitTest {
static int i = 2;
{
i *= 2;
}
public InitTest(){
System.out.println("构造函数:" + i);
}
public static void main(String[] args) {
System.out.println(i);
InitTest test = new InitTest();
System.out.println(i);
}
{
i *= 3;
}
}
运行结果:
2
构造函数:12
12
构造代码块是在对象创建的时候执行的,如果没有创建对象不会执行构造代码块,而且构造代码块执行顺序先于构造函数的执行顺序。
构造代码块的作用是给对象进行初始化
构造代码块与构造函数的区别是:构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化,构造函数可以多次调用,而构造代码块第一次创建对象就会调用,而且仅一次
静态代码块
public class InitTest {
static int i = 2;
static{
i *= 2;
}
public static void main(String[] args) {
System.out.println(i);
}
static{
i *= 3;
}
}
结果:
12
静态代码块随着类的加载而执行,只执行一次,并且优先于主函数。具体说,静态代码块是由类调用的。类调用时,先执行静态代码块,然后才执行主函数的。
静态代码块其实就是给类初始化的,而构造代码块是给对象初始化的。
单个类中例子
public class InitTest {
InitTest() {
System.out.println("构造函数");
}
{
System.out.println("构造代码块");
}
static {
System.out.println("静态代码块");
}
public static void main(String[] args) {
InitTest test1 = new InitTest();
InitTest test2 = new InitTest();
}
}
结果:
静态代码块
构造代码块
构造函数
构造代码块
构造函数
结论:执行顺序从先到后为: 静态变量->静态代码块->变量->构造代码块-> 构造函数
继承中的初始化顺序
父类:
package com.hq.initTest;
/**
* 父类
*@author Huangqing
*@date 2018/6/27 15:54
*/
public class A {
static {
System.out.println("父类:静态代码块");
}
{
System.out.println("父类: 构造代码块");
}
public A(){
System.out.println("父类:构造函数");
}
private static String staticParentString = staticInitParent();
private String parentString = initParent();
public static String staticInitParent(){
System.out.println("父类:静态方法,被静态成员变量赋值调用");
return "staticInitParent";
}
public String initParent() {
System.out.println("父类:普通成员方法,被普通成员变量赋值调用");
return "initParent";
}
}
子类:
package com.hq.initTest;
/**
* 子类
*@author Huangqing
*@date 2018/6/27 15:54
*/
public class B extends A {
private static String staticChildString = staticInitChild();
private String childString = initChild();
static {
System.out.println("子类:静态代码块");
}
{
System.out.println("子类: 构造代码块");
}
public B(){
System.out.println("子类:构造函数");
}
public static String staticInitChild(){
System.out.println("子类:静态方法,被静态成员变量赋值调用");
return "staticInitParent";
}
public String initChild() {
System.out.println("子类:普通成员方法,被普通成员变量赋值调用");
return "initParent";
}
}
主方法:
package com.hq.initTest;
/**
* 测试Java类的成员和初始化块(代码块)初始化顺序
*@author Huangqing
*@date 2018/6/27 15:53
*/
public class InitTest {
public static void main(String[] args) {
System.out.println("主函数执行开始");
new B();
System.out.println("主函数执行结束");
}
}
运行结果:
主函数执行开始
父类:静态代码块
父类:静态方法,被静态成员变量赋值调用
子类:静态方法,被静态成员变量赋值调用
子类:静态代码块
父类: 构造代码块
父类:普通成员方法,被普通成员变量赋值调用
父类:构造函数
子类:普通成员方法,被普通成员变量赋值调用
子类: 构造代码块
子类:构造函数
主函数执行结束
经测试,得到在继承关系中,父类和子类成员初始化执行顺序结论:
1.父类【静态成员】和【静态代码块】,按在代码中出现的顺序依次执行。
2.子类【静态成员】和【静态代码块】,按在代码中出现的顺序依次执行。
3.父类的【普通成员变量被普通成员方法赋值】和【普通代码块】,按在代码中出现的顺序依次执行。
4.执行父类的构造方法。
5.子类的【普通成员变量被普通成员方法赋值】和【普通代码块】,按在代码中出现的顺序依次执行。
6.执行子类的构造方法。
附上一道阿里的Java初始化执行顺序题
public class TestConstructOrder {
public static int k = 0;
public static int k1;
public int k2;
public static TestConstructOrder t1 = new TestConstructOrder("t1");
public static TestConstructOrder t2 = new TestConstructOrder("t2");
public static int i = print("i");
public static int n = 99;
public int j = print("j");
{
print("构造块");
}
static {
print("静态块");
}
public TestConstructOrder(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) {
TestConstructOrder t = new TestConstructOrder("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
整个的执行步骤:(下面的分析不包括父类内容)
加载类信息:
1、静态变量默认初始化
2、对静态变量赋值,加载静态代码块中内容 (赋值顺序按书写顺序)
创建对象:
1、实例变量初始化
2、实例变量赋值(依然按顺序)
3、构造代码块中内容
4、构造函数中内容
分析:
1、 首先Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象。
2、加载类信息,对静态变量默认初始化,此时并未赋值(初始化顺序与代码顺序无关,具体可能要研究JVM虚拟机)
public static int k // 初始化, k = 0
public static int k1; // 初始化,k1 = 0
public static TestConstructOrder t1 = null // 初始化类 t1, t1 = null
public static TestConstructOrder t2 = null // 初始化类 t2, t2 = null
public static int i // 初始化 i = 0
public static int n = 99; // 初始化 n = 0
3、对静态变量赋值,加载静态代码块中内容。
k = 0 // k赋值0
t1 = new TestConstructOrder (“t1”) // 这里创建一个对象
注意 这里因为要创建对象,所以会执行创建对象的步骤:
实例变量初始化:
public int k2;// 初始化k2 , k2 = 0
public int j; // 初始化j, j = 0
实例变量赋值:
j = print(“j”) // 会执行静态方法
打印结果:1:j i = 0 n = 0
因为在创建对象,而我们前面说过,在创建对象时,构造代码块先于构造函数执行,所以会执行构造代码块
打印结果:2:构造块 i = 1 n = 1
然后执行构造函数
打印结果:3:t1 i = 2 n = 2
同样的,给t2赋值也会跟t1一样
打印结果:
4:j i = 3 n = 3
5:构造快 i = 4 n = 4
6:t2 i = 5 n = 5
继续赋值:i = print(“i”)
打印结果:
7:i i = 6 n = 6
对n赋值:int n = 99 // 此时 n = 99
然后执行静态代码块
打印结果:
8:静态块 i = 7 n = 99
最后:执行主函数,新建对象,这里的初始化顺序不再多说了,跟t1的相同
打印结果:
9:j i = 8 n = 100
10:构造快 i = 9 n = 101
6:init i = 10 n = 102
相信这道题,已经足够让你了解Java类成员的初始化顺序,博主也是一开始被弄糊涂了,如果还有不明白的地方,欢迎留言。如果觉得写得不错,别忘了给点个赞~