类加载机制,类初始化时各个成员执行的顺序,静态代码块,静态方法,静态成员变量
类的加载机制
类的加载机制:将编译之后对应的.class文件加载进内存中,并对数据进行校验,解析和初始化,最终形成可被虚拟机直接使用的Java类型
类加载过程
类加载总共有七个步骤:
装载(加载)–>验证–>准备–>解析–>初始化–>使用–>卸载
而验证,准备,解析又可看成一个步骤 连接
装载(加载):
这里是引用加载阶段是类加载过程的第一个阶段。在这个阶段,JVM 的主要目的是将字节码从各个位置(网络、磁盘等)转化为二进制字节流加载到内存中,接着会为这个类在 JVM 的方法区创建一个对应的 Class 对象,这个 Class 对象就是这个类各种数据的访问入口
简单来讲就是将.class文件加载至内存中
校验(验证):确保被加载的类的正确性
准备:为类的静态变量分配内存,并将其初始化为默认值,但此时的初始化时初始化类中的静态成员变量成它声明的对应数据类型的默认值,如int型默认值为0,String型默认值为null
解析:把类中的符号引用转换为直接引用
初始化:对代码中有显式初始化的变量进行初始化
使用:初始化完成后,从入口方法处执行程序
卸载:程序执行完毕,Class被垃圾回收
类中的成员
Java类中的类成员包括:属性,方法,代码块,构造器,内部类
属性,方法,代码块由是否有static修饰分为静态和非静态
初始化顺序
当一个类进行加载时,首先要加载静态成员变量,然后在对类进行实例化时加载非静态成员变量和构造器,如果加载的类有继承关系,那么先加载直接父类,而父类中的加载顺序也是从静态开始进行加载
而同属于静态成员,它们的执行顺序就由代码的书写顺序来决定
下面以一个例子来验证成员的加载顺序
//父类代码
public class Father {
private int i=test();//通过方法的返回值对变量进行初始化
private static int j=method();//通过方法的返回值对变量进行初始化
static{//静态代码块
System.out.println("父类静态代码块1");
}
{//非静态代码块
System.out.println("父类非静态代码块2");
}
Father(){//构造器
System.out.println("父类构造器3");
}
public int test(){//普通方法
System.out.println("父类普通方法4");
return 1;
}
public static int method(){//静态方法
System.out.println("父类静态方法5");
return 2;
}
}
//子类代码,继承了Father类
public class Son extends Father {
private int i=test();//通过方法的返回值对变量进行初始化
private static int j=method();//通过方法的返回值对变量进行初始化
static{//静态代码块
System.out.println("子类静态代码块6");
}
{//非静态代码块
System.out.println("子类非静态代码块7");
}
Son(){//构造器
System.out.println("子类构造器8");
}
public int test(){//普通方法
System.out.println("子类普通方法9");
return 1;
}
public static int method(){//静态方法
System.out.println("子类静态方法10");
return 2;
}
public static void main(String[] args){
}
}
此时运行得到的结果
父类静态方法5
父类静态代码块1
子类静态方法10
子类静态代码块6
仅运行main方法,但在main方法中什么都没做,那么只会对main方法所在的类的静态成员进行加载,则只会运行
private static int j=method();//通过方法的返回值对变量进行初始化
//和
static{//静态代码块
System.out.println("子类静态代码块6");
}
而又由于Son类继承于Father,所以Father类中的静态成员也会被初始化
private static int j=method();//通过方法的返回值对变量进行初始化
static{//静态代码块
System.out.println("父类静态代码块1");
}
由父类先执行,然后再执行子类,最终得到5,1,10,6的结果
然后如果实例化一个Son对象
public static void main(String[] args){
Son s1 = new Son();
}
}
得到如下结果
父类静态方法5
父类静态代码块1
子类静态方法10
子类静态代码块6
子类普通方法9
父类非静态代码块2
父类构造器3
子类普通方法9
子类非静态代码块7
子类构造器8
首先初始化静态成员,得到5,1,10,6的顺序,
然后实例化对象时,先初始化非静态成员,再初始化构造器,又因为子父类的关系,则先执行父类中的非静态成员变量,父类构造器
父类在初始化非静态成员时,按照顺序执行如下代码
private int i=test();//通过方法的返回值对变量进行初始化
{//非静态代码块
System.out.println("父类非静态代码块2");
}
i的初始化由方法test的返回值赋值,但test方法由和子类的test构成一个方法的重写,那么由于多态性,父类调用的就是子类重写过的方法
非静态执行完,执行构造器
Father(){//构造器
System.out.println("父类构造器3");
}
父类执行完毕,子类开始执行
private int i=test();//通过方法的返回值对变量进行初始化
{//非静态代码块
System.out.println("子类非静态代码块7");
}
Son(){//构造器
System.out.println("子类构造器8");
}
再new一个对象,非静态成员和构造器就还会再执行一次
public static void main(String[] args){
Son s1 = new Son();
Son s2 = new Son();
}
父类静态方法5
父类静态代码块1
子类静态方法10
子类静态代码块6
子类普通方法9
父类非静态代码块2
父类构造器3
子类普通方法9
子类非静态代码块7
子类构造器8
子类普通方法9
父类非静态代码块2
父类构造器3
子类普通方法9
子类非静态代码块7
子类构造器8
类中静态的成员只会在类第一次加载的时候初始化一次,而非静态成员和构造器执行在次数在于实例化对象的个数,实例化多少个对象就执行多少次
各个成员执行的顺序:由静态到非静态,由父类到子类,静态之间按代码书写顺序,非静态之间按代码书写顺序,构造器排在非静态之后
关于方法的重写:子父类之间,子类和父类有同名同参的方法,那么子类中的方法是对父类的方法的重写,一旦子类重写了父类的方法,父类在调用被重写的方法是,实际执行的是子类中的重写方法,这是多态性的表现
但方法的重写有一定的限制,被final修饰的方法不能被重写,static方法不构成重写,父类中不可见的方法不可重写
本文地址:https://blog.csdn.net/weixin_44736603/article/details/109984047
上一篇: 农村“信息化”成为现代农业发展新引擎