Java代码加载顺序
- 代码块
- 类加载和初始化相关案例一
- 案例二
- 深度加载知识
- 静态变量
- 内部类中不能定义静态变量
代码块
项目 | Value | 作用 |
局部代码块 | 在main方法里面 | 给变量限定声明周期,局部代码块的变量在执行结束后会被Java回收 |
构造代码块(非静态代码块) | 在类的成员位置 | 在每次执行构造方法前先执行构造代码块。可以将多个构造方法中的相同的代码放到构造代码块中,对对象进行初始化。 |
静态代码块 | 在类的成员位置 | 一般用于给类初始化,被静态修饰的代码块仅执行一次。 |
1、代码执行顺序:静态代码块—>main方法的代码 —>构造代码块 —>构造方法 —>顺序执行代码(或同步代码块)。
2、父类静态代码块 —> 子类静态代码块 —> main方法的代码 —> 父类构造代码块 —> 父类构造方法 —> 子类构造代码块 —> 子类构造方法 —> 顺序执行代码(包括普通代码块、同步代码块)(当父类无super时,触发子类有参构造函数和无参构造函数时只调用父类无参构造函数)
类加载和初始化相关案例一
package classloader;
public class TestStatic {
static {
//1.main方法所在类的静态代码块,打印static;
System.out.println("static");
}
//开始执行main方法;
public static void main(String[] args) {
//2.执行main方法中的代码。new一个B的实例,进入到类B;
new ClassB();
}
}
class ClassA{
ClassD d;
static {
//3.从ClassB进入从父类ClassA的静态代码块,打印ClassA 1;
System.out.println("ClassA 1");
}
{
//7.执行父类的构造代码块,打印ClassA 2;
System.out.println("ClassA 2");
//8.此处开始new对象(非静态相关)。
d = new ClassD();
}
public ClassA(){
//11.执行完ClassA的构造代码块,执行构造方法,打印ClassA 3;
System.out.println("ClassA 3");
}
}
//ClassB有父类ClassA
class ClassB extends ClassA{
//4.调用完了父类的静态相关来到子类的静态相关
static ClassC classC = new ClassC();
static {
//6.按照顺序来调用自己的静态代码块,到此子类的所有静态都执行完毕接下来将会执行非静态相关,打印ClassB 1;
System.out.println("ClassB 1");
}
{
//12.执行完父类构造方法,执行子类的非静态块,打印ClassB 2;
System.out.println("ClassB 2");
}
public ClassB() {
//13.调用完自己的非静态块调用自己的构造方法,打印ClassB 3。
System.out.println("ClassB 3");
}
}
class ClassC{
public ClassC() {
//5.new ClassC的时候,ClassC没有父类,没有静态代码块,没有构造代码块,直接执行构造方法,打印ClassC;
//9.在new ClassD的时候,ClassD有父类ClassC,父类中没有静态代码,子类中没有静态代码;父类中也没有构造代码块,执行父类的构造方法,打印ClassC;
System.out.println("ClassC");
}
}
//ClassD有父类 ClassC
class ClassD extends ClassC{
public ClassD() {
//10.调用完父类构造方法,由于子类没有构造代码块,直接执行子类构造方法,打印ClassD
System.out.println("ClassD");
}
}
- 当执行TestStatic类中的main方法时,由于该类没有父类,但是有静态代码块,所以先执行静态代码块中的代码,在控制台打印static;
- 执行main方法中的代码,初始化一个ClassB,由于ClassB有父类,所以先调用父类ClassA中的静态代码块,所以在控制台打印ClassA 1;
- 执行子类即ClassB中的静态代码块,ClassC的实例化与初始化。ClassC没有父类,没有静态代码块,没有构造代码块,直接执行ClassC中的构造方法,在控制台打印ClassC;
- 回到ClassB,继续执行静态代码块,在控制台打印 ClassB1;
- 执行完ClassB的静态代码,执行父类ClassA的构造代码块,在控制台打印ClassA 2;
- 执行父类ClassA的构造代码块是初始化一个ClassD,ClassD有父类ClassC,父类ClassC中没有静态代码块,ClassD中也没有静态代码块,父类ClassC中也没有构造代码块,执行构造方法,在控制台打印ClassC;
- 执行完父类ClassC中的构造方法,由于子类ClassD中没有构造代码块,执行构造方法,在控制台打印ClassD;
- 回到ClassA,执行完构造代码块,执行构造方法,在控制台打印ClassA 3;
- 再回到ClassB中,执行构造代码块,在控制台打印ClassB 2;
10.执行ClassB的构造方法,在控制台打印ClassB 3。
案例二
package classloader;
public class Text {
//1)执行main方法
public static void main(String[] args) {
// 24)执行main方法中的代码,直接调用构造方法
Text t = new Text("init");
}
//2)首先执行main方法所在类的静态相关代码, k = 0;
public static int k = 0;
//3)这里的静态属性赋有一个非静态的对象 所以停止类加载转向所有的非静态初始化
public static Text t1 = new Text("t1");
// 9)由于上一句静态代码以及相关的非静态代码执行完毕所以来到了下一句静态代码的执行
public static Text t2 = new Text("t2");
// 15) 由于上一句静态代码以及相关的非静态代码执行完毕所以来到了下一句静态代码的执行
public static int i = print("i");
//17) 由于上一句静态代码以及相关的非静态代码执行完毕所以来到了下一句静态代码的执行
public static int n = 99;
// 4)这是第一个非静态属性它的赋值调用print("j")方法
// 10)原理和3)的执行过程类似,调用print("j")
// 20)执行非静态代码,调用print("j")
public int j = print("j");
{
// 6)现在4)处的非静态属性终于初始化完毕,所以接着来到了非静态块
// 12)现在10)处的非静态属性初始化完毕,执行非静态代码块
// 22)执行非静态代码块
print("构造块");
}
static {
//18)执行该静态代码块
print("静态块");
}
// 8)非静态块执行完毕之后来到了构造方法(它本身也是非静态的)此时k = 2, i = 2, n = 2, 打印 3 : t1 i = 2 n = 2;
// 14) 非静态块执行完毕之后来到了构造方法(它本身也是非静态的)此时k = 5, i = 5, n = 5, 打印 6 : t2 i = 5 n =5
// 25) 类加载完毕,直接调用构造方法,此时k = 10, i = 10, n = 102,打印 11 : init i = 10 n = 102
public Text(String str) {
System.out.println((++k) + ":" + str + " i = " + i + " n = " + n);
++i;
++n;
}
// 5)来自4)的调用此时k = 0, i = 0, n = 0, 打印 1 : j i = 0 n = 0
// 7)来自6)的调用此时k = 1, i = 1, n = 1, 打印 2 : 构造块 i = 1 n = 1
// 11) 来自10)的调用此时 k = 3, i = 3, n = 3, 打印 4 : j i = 3 n = 3
// 13)来自12)的调用此时 k = 4, i = 4, n = 4, 打印 5 : 构造块 i = 4 n = 4
// 16)来自15)的调用此时 k = 6, i = 6, n = 6, 打印 7 : i i = 6 n = 6
// 19)来自18)的调用此时 k = 7, i = 7, n = 99,打印 8 : 静态块 i = 7 n = 99
// 21)来自20)的调用此时 k = 8, i = 8, n = 100,打印 9 : j i = 8 n = 100
// 23)来自22)的调用此时 k = 9, i = 9, n = 101,打印 10 : 构造快 i = 9 n = 101
public static int print(String str) {
System.out.println((++k) + ":" + str + " i = " + i + " n = " + n);
++n;
return ++i;
}
}
注意:当加载到一个静态属性的时候他的赋值对象为一个静态的对象,这个时候就会中断静态相关的加载,转而先去执行非静态相关的代码。这里还需要注意的是属性和代码块的加载遵循他们的先后出场顺序。
深度加载知识
所有的类加载
- 先进行解析(也就是声明静态变量但是不去初始化),也就是将静态变量放入方法区并且标记,标记一个值0。相当于只定义没有赋值。
- 当所有的解析都过去的时候才进行初始化,初始化就是按照出场顺序来执行静态代码块和检查静态变量那里是否赋值,如果有值的话那么就赋值,没有的话那么就将标记值赋值给静态变量。
- 标记状态的值相当于无值,它不可以直接参加运算但是可以间接的使用标记的值。类名调用。
静态变量
static修饰的变量是静态变量。静态变量在类加载的时候加载到方法区,并且在方法区中被赋予默认值。由于静态变量先于对象出现,所以可以通过类名来调用静态变量,也可以通过对象调用。这个类的所有对象存储的是这个静态变量在方法区中的地址,所以所有对象共享这个静态变量。
注意:
- 类是加载到方法区中—类中的所有信息都会加载方法区中
- 类是第一次使用的时候加载到方法区,加载之后不在移除—意味着类只加载一次
- 静态变量不能定义在构造方法中。静态变量在类加载的时候加载到方法区;构造方法是在创建对象的时候调用,在栈内存中执行。
- 静态变量不能定义在构造代码块中。所有的静态变量只能定义在类中,不能定义在代码块中,代码块也就意味着这个变量的作用域只是在当前代码块的作用域内。
内部类中不能定义静态变量
静态变量是要占用内存的,在编译时只要是静态变量,系统就会自动分配内存给他,而内部类是在宿主类编译完编译的,也就是说,必须有宿主类存在后才能有内部类,这也就和编译时就为静态变量分配内存产生了冲突,因为系统执行:运行宿主类—>静态变量内存分配—>内部类,而此时内部类的静态变量先于内部类生成,这显然是不可能的,所以内部类中不能定义静态变量。