java有枚举类:
// 定义一个星期的枚举类
public enum WeekEnum {
// 在第一行显式地列出7个枚举实例(枚举值),
//系统会自动添加 public static final 修饰
MONDAY, TUESDAY, WEDNESDAY;
}
枚举类本质是:
public final class WeekEnum extends Enum {
...
public static final WeekEnum MONDAY;
public static final WeekEnum TUESDAY;
public static final WeekEnum WEDNESDAY;
...
}
可以看到存在一种情况一个类声明了一个静态的自身类型的变量,这篇文章就是探讨,在这种情况下,类是如何让加载的。
先了解下普通类的加载顺序。
测试用代码:
package main.java.javaRun.StaticClassInit;
/**
* @Author: xxx
* @Description: 测试类初始化顺序
* @Date: Created in 9:17 2020/8/31
*/
public class StaticInit {
static class MyLog { //赋值语句执行时打印提示信息
public MyLog(String mes) {
System.out.println(mes);
}
}
//类A,用来作为父类
static class A {
public A() {
System.out.println("+++调用父类A的构造方法+++");
}
{
System.out.println("父类A 1号普通语句块 执行--->");
}
static {
System.out.println("父类A 1号静态语句块 执行--->");
}
MyLog varA = new MyLog("--->父类A 普通成员变量varA 被赋值");
public static MyLog staA = new MyLog("--->父类A 静态变量staA 被赋值");
static {
System.out.println("父类A 2号静态语句块 执行--->");
}
{
System.out.println("父类A 2号普通语句块 执行--->");
}
}
//类B,测试该类的记载情况
static class B extends A {
static {
System.out.println("子类B 静态语句块 执行--->");
}
{
System.out.println("子类B 普通语句块 执行--->");
}
public B() {
System.out.println("+++调用子类B的构造方法+++");
}
}
public static void main(String[] args) {
System.out.println("=======第一次构建B类对象=======");
new B();
System.out.println("\n=======第二次构建B类对象=======");
new B();
}
}
/*测试结果:
*=======第一次构建B类对象=======
*父类A 1号静态语句块 执行--->
*--->父类A 静态变量staA 被赋值
*父类A 2号静态语句块 执行--->
*子类B 静态语句块 执行--->
*父类A 1号普通语句块 执行--->
*--->父类A 普通成员变量varA 被赋值
*父类A 2号普通语句块 执行--->
*+++调用父类A的构造方法+++
*子类B 普通语句块 执行--->
*+++调用子类B的构造方法+++
*
*=======第二次构建第B类对象=======
*父类A 1号普通语句块 执行--->
*--->父类A 普通成员变量varA 被赋值
*父类A 2号普通语句块 执行--->
*+++调用父类A的构造方法+++
*子类B 普通语句块 执行--->
*+++调用子类B的构造方法+++
*/
结论:
按顺序分成两个阶段:
一、类加载后进行的初始化
1. 执行父类静态语句块和对父类的静态变量赋值
2. 执行自身静态语句块和对自身的静态变量赋值
注意:类的初始化只在类加载时进行一次,以后实例化对象不需要重新进行类的初始化。
二、实例化对象
1. 执行父类的赋值语句和初始化语句块。
2. 执行父类的构造方法。
3. 执行自身的赋值语句和初始化语句块。
4. 执行自身的构造方法。
注意:同一个类中,构造方法的优先级比成员变量的赋值语句和初始化块低。
再看下自嵌套类的加载顺序
测试用代码:
package main.java.javaRun.StaticClassInit;
/**
* @Author: xxx
* @Description: 测试自嵌套类的初始化
* @Date: Created in 9:51 2020/8/31
*/
class StaticVarNewItself {
private static String time = "构造方法尚未执行过";
public StaticVarNewItself(String time) {
this.time = time;
System.out.println("构造方法执行,时间:" + time);
}
{
System.out.println("1号普通语句块执行时机:" + time);
}
// new自身类对象来初始化静态变量
static {
System.out.println("1号静态语句块执行时机:" + time);
}
public static StaticVarNewItself t1 = new StaticVarNewItself("+++类在自身内部被初始化+++");
//注意:t2不能赋值否则栈溢出,因为t2不是静态变量会多次加载会成死循环
public StaticVarNewItself t2;
{
System.out.println("2号普通语句块执行时机:" + time);
}
static {
System.out.println("2号静态语句块执行时机:" + time);
}
}
public class StaticOneselfInit {
static {
System.out.println("main方法所在的类被加载\n");
}
public static void main(String[] args) {
StaticVarNewItself a1,a2;
a1 = new StaticVarNewItself("+++类第一次实例化+++");
System.out.println("\n -----------------分割线---------------- \n");
a2 = new StaticVarNewItself("+++类第二次实例化+++");
}
}
/*测试结果:
* main方法所在的类被加载
*
* 1号静态语句块执行时机:构造方法尚未执行过
* 1号普通语句块执行时机:构造方法尚未执行过
* 2号普通语句块执行时机:构造方法尚未执行过
* 构造方法执行,时间:+++类在自身内部被初始化+++
* 2号静态语句块执行时机:+++类在自身内部被初始化+++
* 1号普通语句块执行时机:+++类在自身内部被初始化+++
* 2号普通语句块执行时机:+++类在自身内部被初始化+++
* 构造方法执行,时间:+++类第一次实例化+++
*
* -----------------分割线----------------
*
* 1号普通语句块执行时机:+++类第一次实例化+++
* 2号普通语句块执行时机:+++类第一次实例化+++
* 构造方法执行,时间:+++类第二次实例化+++
* */
从以上结果可以分析出静态的自身类型的变量是作为一个普通的静态变量处理的,具体流程我个人分析如下(也是分为加载和实例化两个过程):
1. 类a1通过new开始实例化,检查内存,没有发现自己的Class,因此先进行类的初始化。
2. 类a1开始执行父类静态语句块和对父类的静态变量赋值(本例中没有父类)。
3. 类a1开始执行自身静态语句块和对自身的静态变量赋值。
1号静态语句块执行时机:构造方法尚未执行过
4. 执行到静态的自身类型的变量即t1 。
5. 检查发现内存内有自己的Class(虽然该类还未初始化完),开始进行实例化。
1号普通语句块执行时机:构造方法尚未执行过
2号普通语句块执行时机:构造方法尚未执行过
构造方法执行,时间:+++类在自身内部被初始化+++
==========================================================
注意:
此时该类的初始化还未完成,因为2号静态语句块还未执行过。
此时没有再执行t1内的t1,因为t1是静态变量,jvm只执行一次
6. 等t1实例化后,接着进行类的初始化。
2号静态语句块执行时机:+++类在自身内部被初始化+++
7. 此时实例化a1前的类初始化工作已经完成,开始实例化。
1号普通语句块执行时机:+++类在自身内部被初始化+++
2号普通语句块执行时机:+++类在自身内部被初始化+++
构造方法执行,时间:+++类第一次实例化+++
8. 此时a1完成实例化。
9. 当再次实例化该类,例如a2时,该类就只执行普通语句块和普通赋值语句了。
1号普通语句块执行时机:+++类第一次实例化+++
2号普通语句块执行时机:+++类第一次实例化+++
构造方法执行,时间:+++类第二次实例化+++