在Java类的初始化中,除了构造函数,还有构造块、静态构造块、静态域的初始化,那么在一个继承链中,它们初始化代码的执行顺序又是什么样的呢?
我们先做一个简单的测试,假设现在有三个类,你爷爷、你爸爸和你。
package dailyprg0801;
public class Grandpa {
public static int Age = 80;
public Grandpa(){
System.out.println("执行爷爷构造函数");
}
static{
System.out.println("执行爷爷静态块");
}
{
System.out.println("执行爷爷构造块");
}
public static void say(){}
}
package dailyprg0801;
public class Dad extends Grandpa {
public static int Age = 50;
public Dad(){
System.out.println("执行爸爸构造函数");
}
{
System.out.println("执行爸爸构造块");
}
static{
System.out.println("执行爸爸静态块");
}
public static void say(){}
}
package dailyprg0801;
public class You extends Dad {
public static final int Age = 20;
public You(){
System.out.println("执行你的构造函数");
}
static{
System.out.println("执行你的静态块");
}
{
System.out.println("执行你的构造块");
}
public static void say(){}
}
测试一个,当我们new一个You的对象时
package dailyprg0801;
public class InitTest {
public static void main(String[] args) {
new You();
}
}
打印结果如下
PS C:\Users\xxx\eclipse-workspace\dailyprg0801\bin> java dailyprg0801.InitTest
执行爷爷静态块
执行爸爸静态块
执行你的静态块
执行爷爷构造块
执行爷爷构造函数
执行爸爸构造块
执行爸爸构造函数
执行你的构造块
执行你的构造函数
如果我们创建两个You的对象呢
package dailyprg0801;
public class InitTest {
public static void main(String[] args) {
new You();
new You();
}
}
看下结果
PS C:\Users\xxx\eclipse-workspace\dailyprg0801\bin> java dailyprg0801.InitTest
执行爷爷静态块
执行爸爸静态块
执行你的静态块
执行爷爷构造块
执行爷爷构造函数
执行爸爸构造块
执行爸爸构造函数
执行你的构造块
执行你的构造函数
执行爷爷构造块
执行爷爷构造函数
执行爸爸构造块
执行爸爸构造函数
执行你的构造块
执行你的构造函数
在我们用new关键字调用子类的构造器的时候,当我们的第一次调用时,会对这个类进行加载并初始化。而在初始化这个类的时候,如果发现其父类还没有进行过初始化,需要先触发其父类的初始化。
多以我们会看到先执行爷爷的静态块,然后是爸爸,最后才是你的静态块,这里是因为子类的静态初始化可能会依赖于父类成员能否被正确初始化。
类初始化完成后,new关键字调用You的构造器,这时进行对象的初始化,由于在调用子类构造器的时候,默认会调用父类的无参构造器,而初始化代码块又只在对象初始化时才执行,而且在构造函数之前执行,导致我们看到 下面的顺序。
执行爷爷静态块
执行爸爸静态块
执行你的静态块
执行爷爷构造块
执行爷爷构造函数
执行爸爸构造块
执行爸爸构造函数
执行你的构造块
执行你的构造函数
而静态块只执行一次,也就是在类加载的时候,所以第二次再创建You对象时,没有看到静态初始化块的执行。
另外,当访问You的静态域时
package dailyprg0801;
public class InitTest {
public static void main(String[] args) {
int age = You.Age;
}
}
看下结果
PS C:\Users\xxx\eclipse-workspace\dailyprg0801\bin> java dailyprg0801.InitTest
执行爷爷静态块
执行爸爸静态块
执行你的静态块
或者调用静态方法
package dailyprg0801;
public class InitTest {
public static void main(String[] args) {
You.say();
}
}
结果是一样的
PS C:\Users\xxx\eclipse-workspace\dailyprg0801\bin> java dailyprg0801.InitTest
执行爷爷静态块
执行爸爸静态块
执行你的静态块
调用或者设置一个类的静态域,以及调用一个类的静态方法时,和new一样,都会触发类的初始化,但是这里没有调用构造器,所以只看到了类的初始化,没有看到对象的初始化。这个过程和下面进行的类加载是一样的
package dailyprg0801;
public class InitTest {
public static void main(String[] args) {
//new You();
//new You();
try{
Class.forName("dailyprg0801.You");
}catch(Exception e){
e.printStackTrace();
}
System.out.println("============分割线===========");
new You();
}
}
其结果
PS C:\Users\xxx\eclipse-workspace\dailyprg0801\bin> java dailyprg0801.InitTest
执行爷爷静态块
执行爸爸静态块
执行你的静态块
============分割线===========
执行爷爷构造块
执行爷爷构造函数
执行爸爸构造块
执行爸爸构造函数
执行你的构造块
执行你的构造函数
再次验证了静态块只在类初始化时执行一次。这里我们可以通过-XX:+TraceClassLoading 参数观察下类加载的过程
PS C:\Users\xxx\eclipse-workspace\dailyprg0801\bin> java -XX:+TraceClassLoading dailyprg0801.InitTest
******************************************
******这里很多其他类的加载过程太长**********
******************************************
[Loaded dailyprg0801.Grandpa from file:/C:/Users/xxx/eclipse-workspace/dailyprg0801/bin/]
[Loaded dailyprg0801.Dad from file:/C:/Users/xxx/eclipse-workspace/dailyprg0801/bin/]
[Loaded dailyprg0801.You from file:/C:/Users/xxx/eclipse-workspace/dailyprg0801/bin/]
执行爷爷静态块
执行爸爸静态块
执行你的静态块
============分割线===========
执行爷爷构造块
执行爷爷构造函数
执行爸爸构造块
执行爸爸构造函数
执行你的构造块
执行你的构造函数
[Loaded java.lang.Shutdown from C:\Program Files\Java\jdk1.8.0_162\jre\lib\rt.jar]
[Loaded java.lang.Shutdown$Lock from C:\Program Files\Java\jdk1.8.0_162\jre\lib\rt.jar]
但当访问静态常量的时候,如果You的Age是final的
public static final int Age = 20;
看下结果
PS C:\Users\xxx\eclipse-workspace\dailyprg0801\bin> java dailyprg0801.InitTest
20
可以看到几个类的初始化都没有执行。使用-XX:+TraceClassLoading 会看到这三个类都没加载。
这是因为类中的静态常量在编译阶段通过常量传播优化,Age这个常量被存到NotInitialization 类的常量池中了,就是搞的常量跟它原来所在的类没啥关系了,因为常量吗,在哪都还是那个它,所以就不会触发类的初始化。
emmmmm,本来觉得脑子里堆了一堆东西才想写出来理一下,写到一半感觉这样写太局限了,应该结合JVM一起讲,做个标记,下次再补。