关于类加载和初始化相关的案例
总的顺序是:先父类后子类,先静态后动态,属性和代码块的初始化遵循正常的出场顺序无论是静态还是动态,但是他们总是先于构造器执行。但是还是需要通过题目的学习来加深我们的理解。
案例一
package ooptest;
public class StaticDemo6 {
public static void main(String[] args) {
new SB();
}
}
class SA {
D d;
static {
System.out.println("A 1"); // 1.先从父类的静态开始
}
{
System.out.println("A 2"); // 5.此处开始new对象(非静态相关)
d = new D();// 6.顺序执行
}
public SA() {
System.out.println("A 3"); // 10.此时调用完了自己的非静态代码块来到了构造器
}
}
class SB extends SA {
static C c = new C(); // 2.调用完了父类的静态相关来到子类的静态相关
static {
System.out.println("B 1"); // 4.接着按照顺序来调用自己的静态代码块 ,到此子类的所有静态都执行完毕接下来将会执行非静态相关
}
{
System.out.println("B 2"); // 11.父类的构造器调用完成调用子类的非静态块
}
public SB() {
System.out.println("B 3"); // 12.调用完了自己的非静态块调用自己的构造方法
}
}
class C {
public C() {
System.out.println("C"); // 3.C没有父类与静态直接调用自己的构造器 // 8.
}
}
class D extends C {// 7. 来到了D但是D有自己的父类所以到达C类
public D() {
System.out.println("D");// 9.调用完了父类的构造器会来到子类的构造器
}
}
案例二
这里只写明了前三个打印的注释,后面的过程相似。
package staticment;
public class Text {
public static int k = 0;
public static Text t1 = new Text("t1"); // 1.这里的静态属性赋有一个非静态的对象 所以停止类加载转向所有的非静态初始化
public static Text t2 = new Text("t2");// 6.由于上一句静态代码以及相关的非静态代码执行完毕所以来到了下一句静态代码的执行
public static int i = print("i");
public static int n = 99;
public int j = print("j");// 2.这是第一个非静态属性它的赋值调用print("j")方法 // 7.原理和1的执行过程类似
{// 4.现在2.处的非静态属性终于初始化完毕,所以接着来到了非静态块此时 k = 2, i = 1, n = 1
print("构造块");
}
static {
print("静态块");
}
public Text(String str) {// 5.非静态块执行完毕之后来到了构造方法(它本身也是非静态的)此时k = 3, i = 2, n = 2
System.out.println((++k) + ":" + str + " i = " + i + " n = " + n);
++i;
++n;
}
public static int print(String str) {// 3.来自2.的调用此时k = 1, i = 0, n = 0
System.out.println((++k) + ":" + str + " i = " + i + " n = " + n);
++n;
return ++i;
}
public static void main(String[] args) {
Text t = new Text("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
|
这里最最重要的一点就是,当加载到一个静态属性的时候他的赋值对象为一个静态的对象,这个时候就会中断静态相关的加载,转而先去执行非静态相关的代码。这里还需要注意的是属性和代码块的加载遵循他们的先后出场顺序。
静态方法的隐藏
可以通过向上造型来验证,
A a = new B();
a.m(); // 这时执行的是A类中的静态方法。
深度加载知识
无论如何类的加载都
①先进行解析(也就是声明静态变量但是不去初始化),也就是将静态变量放入方法区并且标记,标记一个值0。相当于只定义没有赋值。
②当所有的解析都过去的时候才进行初始化,初始化就是按照出场顺序来执行静态代码块和检查静态变量那里是否赋值值,如果有值得话那么就赋值,没有的话那么就将标记值赋值给静态变量。
注意:标记状态的值相当于无值它不可以直接参加运算但是可以间接的使用标记的值。类名调用。
几个案例
public class StaticDemo5 {
public static void main(String[] args) {
System.out.println(Demo.i);
}
}
class Demo {
static {
i = 7; // 1
}
static int i; // 2
}
执行顺序:
①进行解析,i = 0; // 这里是标记值
③执行到2处检查i是否赋值,结果2处没有赋值,所以就将标量值赋给了i
④i的最终值为7。
案例二
public class StaticDemo6 {
public static void main(String[] args) {
// new SDemo();
System.out.println(SDemo.i);
}
}
class SDemo {
static {
System.out.println(++SDemo.i); // 1
}
static int i = 5;
}
①进行解析,i = 0; // 这里是标记值
②按照静态出现的顺序来加载。执行到1处,此时i的标记值发生了运算它改为了1。
③执行到2处检查i是否赋值,结果将5赋值给了i
④i的最终值为5。
案例3
public class StaticDemo7 {
public static void main(String[] args) {
System.out.println(E.i);
System.out.println(E.j);
}
}
class E {
static E e = new E();// 1
static int i = 5;// 2
static int j;// 3
static {
E.i++;
E.j++;
}
public E() {
i++;
j++;
}
}
①首先进行解析 i = 0,j = 0 //标记值
②停止静态的类加载,执行构造器中的方法,标记值发生了运算,i = 1,j = 1
③执行到2处,i = 5
④执行到3处j没有赋值,默认使用标记值1
⑤执行静态代码块i = 6,j = 2
内部类与加载顺序
内部类中不能含有静态变量
静态变量是要占用内存的,在编译时只要是定义为静态变量了,系统就会自动分配内存给他,而内部类是在宿主类编译完编译的,也就是说,必须有宿主类存在后才能有内部类,这也就和编译时就为静态变量分配内存产生了冲突,因为系统执行:运行宿主类->静态变量内存分配->内部类,而此时内部类的静态变量先于内部类生成,这显然是不可能的,所以不能定义静态变量!
使用局部代码块的优点
初始化代码块/构造代码块 --- 定义在类内 --- 在创建对象的时候先于构造方法来执行一次。
局部代码块 --- 定义方法中 --- 限制变量的生命周期,以提高栈内存的利用率
静态变量
static修饰变量---静态变量/类变量。静态变量在类加载的时候加载到方法区,并且在方法区中被赋予默认值。由于静态变量先于对象出现,所以可以通过类名来调用静态变量,也可以通过对象调用。这个类的所有对象存储的是这个静态变量在方法区的地址,所以所有对象是共享这个静态变量。
System.out 、System.in
注意:
1. 类是加载到方法区中---类中的所有的信息都会加载方法区中
2. 类是第一次使用的时候加载到方法区,加载之后不在移除 --- 意味着类只加载一次
静态变量能否定义到构造方法中?---不可以。--- 静态变量在类加载的时候加载到方法区;构造方法是在创建对象的时候调用,在栈内存中执行。
静态变量能否定义到构造代码块中?---不可以
注意:所有的静态只能定义在类中不能定义到代码块中,代码块也就意味着这个变量的作用域只是在当前代码块的作用域内。