继承方式下静态成员变量、普通成员变量、静态代码块、构造代码块、构造函数在JVM的加载顺序
目录结构:
-
一、预先一下所需要的知识
- static关键字
- 普通成员变量(实例变量)和静态成员变量(类变量)的区别?
- 三种代码块的区别
-
二、各成员在JVM的加载顺序
- 继承方式下各成员在JVM的加载顺序
- 其他要注意的要点
一、预先一下所需要的知识
Static关键字
首先我们来了解一下Static
这个关键字。
- Static可以用来修饰成员(属性和方法)
- 被Static修饰的成员可以被所有该类的对象共享
- 换句话说,被修饰的成员意味有着全局变量或全局函数的意思。
详见我的另一篇博文:
【Java修饰符之三】Java中static关键字的五种使用方法
代码证明:
public class ClassC {
static int a = 10;
int b = 5;
public void showA(String className){
System.out.println(className+" a="+a+ " b="+b);
}
public void SetA(int a,int b){
this.a = a;
this.b = b;
}
//class1修改了a和b变量。
public static void main(String[] args) {
ClassC class1 = new ClassC();
ClassC class2 = new ClassC();
class1.showA("class1");
class1.SetA(20,10);
class1.showA("class1");
class2.showA("class2");
}
}
结果是:
class1 a=10 b=5
class1 a=20 b=10
class2 a=20 b=5
上面我们可以看到,class1修改了a和b的变量,a是静态成员变量,b是普通成员变量。所以class2调用showA方法的时候,a显示的是class1所修改的值,b则还是classC类初始化时的值。这是因为静态成员将被同一个类的所有对象所共享,生命周期跟随类,而普通成员的生命周期只跟随类的具体的对象。
普通成员变量(实例变量)和静态成员变量(类变量)的区别?
1.生命周期不同
- 普通成员变量随着对象的创建而创建,随着对象的回收而释放
- 静态成员变量随着类的加载而创建,随着类的消失而消失
2.调用方式不同
- 普通成员变量只能够被具体的对象调用
- 静态成员变量可以被对象调用,也可以被类直接调用
3.数据存储位置不同
- 普通成员变量存储堆中对象数据中
- 静态成员变量存储在方法区的类信息中
上面的代码可以证实
三种代码块的区别
1.静态代码块
在类中,方法体外定义,被static所修饰
public class ClassA {
static {System.out.println("static code block");}
}
- 随着类的加载而执行
- 只执行一次
- 可用给类初始化,有的类不想使用构造方法去初始化
2.构造代码块
在类中,方法体外定义,未被static修饰
public class ClassA {
{System.out.println("code block");}
}
- 在创建对象时执行,每创建一个对象就执行一次
- 在创建对象时执行,在构造函数前执行
- 可用于给对象初始化
3.普通代码块(局部代码块)
在方法体内定义的代码块
public class ClassA {
public void show(){
{ System.out.println("局部代码块"); }
}
}
- 限定函数中的局部变量的生命周期
二、各成员在JVM的加载顺序
继承方式下各成员在JVM的加载顺序
ClassA.java
//父类
public class ClassA {
//构造方法
public ClassA() {
System.out.println("ClassA - constructor method running");
}
//构造代码块
{System.out.println("ClassA - code block running");}
//静态代码块
static {System.out.println("ClassA - static code block running");}
//方法
public void show() {
System.out.println("ClassA - show method running!");
}
}
ClassB.java
//子类
public class ClassB extends ClassA{
//构造方法
public ClassB() {
System.out.println("ClassB - constructor method running");
}
//构造代码块
{System.out.println("ClassB - code block running");}
//静态代码块
static {System.out.println("ClassB - static code block running");}
//方法
public void show() {
System.out.println("ClassB - show method running!");
}
public static void main(String[] args) {
ClassB classB = new ClassB();
classB.show();
}
}
结果:
ClassA - static code block running
ClassB - static code block running
ClassA - code block running
ClassA - constructor method running
ClassB - code block running
ClassB - constructor method running
ClassB - show method running!
分析:
主函数实例化了一个ClassB类的对象,又因为ClassB类继承于ClassA类,所以首先加载父类静态成员,再加载子类静态成员(成员变量、代码块)。当父子类静态成员都加载完毕之后,开始加载父类普通成员,加载父类构造函数。加载子类普通成员,加载子类构造函数。
注意:
- 当JVM在加载一个类的成员的时候,会将该类的成员放到构造函数里,原有逻辑之前,构造函数的原有逻辑最后执行。
- 类成员(成员变量、代码块)之间的执行顺序有实际代码顺序决定。
- 静态方法只能访问静态成员(非静态方法可以自由访问),下面会解释
– 静态方法不能使用this和super关键字,下面也会解释
结论:
1、 初始化父类静态成员变量,静态代码块。顺序实际代码顺序决定
2、 初始化子类静态成员变量,静态代码块。顺序实际代码顺序决定
3、 初始化父类的成员变量,构造代码块。顺序实际代码顺序决定
4、 初始化父类的构造函数。
5、 初始化子类的成员变量,构造代码块。顺序实际代码顺序决定
6、 初始化子类的构造函数。
成员变量和代码块是同级的,实际加载顺序由代码位置决定
其他要注意的要点
1.为什么静态方法只能访问静态成员,而不能访问普通成员?
- 举个粟子,因为在类的加载中,静态成员优先于普通成员。所以如果在静态方法中访问普通成员变量。当JVM加载静态方法的时候,要访问的普通成员变量都还没有被JVM加载,根本找不到该变量。所以不能访问普通的成员。
- 但是也有解决方法,那就是让该成员提前给JVM加载,通过new一个对象去调用普通成员,用于给静态方法访问。
Code Demo:
public class ClassA {
int a = 5;
static int b = 10;
//构造方法
public ClassA() {
System.out.println("ClassA - constructor method running");
}
//构造代码块
{System.out.println("ClassA - code block running");}
//静态代码块
static {
ClassA classA = new ClassA();
System.out.println( "ClassA - static code block running" + " a="+classA.a);
}
//方法
public void show() {
System.out.println("ClassA - show method running!");
}
public static void main(String[] args) {
System.out.println("main method running!");
}
}
结果:
ClassA - code block running
ClassA - constructor method running
ClassA - static code block running a=5
main method running!
分析:
为什么会执行了4条输出语句?重点是静态代码块中还有一个类的实例化。
因为这里的执行顺序是:
1. 加载ClassA类的静态成员变量 b ;
2. 加载ClassA类的静态代码块,在加载期间发现其中含有类的实例化,执行ClassA的构造方法,将ClassA类的成员放入原有构造函数逻辑之前。
3. 加载ClassA类的成员变量 a
4. 加载ClassA类的代码块
5. 加载ClassA类的构造函数
6. 输出ClassA类中静态代码块剩余的逻辑,然后ClassA类静态代码块加载完毕。
7. 加载ClassA类主函数部分.
类的实例化对象让成员变量提前加载,所以才能够给静态代码块访问到。
2.为什么静态方法里面不能使用this,super关键字?
1) 首先我们要区分一下类和对象的范畴
类是类,对象是对象,对象是一个类的实例化的结果。
2) 静态成员和普通成员内存的分配不同
静态成员是在类加载的时候就开始加载了,在内存中有块单独的静态内存区用于存储静态的成员,属于类的范畴,类的成员,是独一无二的。普通的成员是在类被实例化的过程中被JVM加载的,属于对象的范畴,相当于是一个副本成员,因为一个类是可以实例化很多个对象,每个对象都拥有属于自己的普通成员。
3) 为什么不能使用this和super关键字呢?
- 静态成员优先于对象的存在,在JVM加载中,静态成员优于主函数中对象的实例化,所以更早被JVM识别。
- this代表该对象,引用的是当前对象,super也差不多,代表该对象的父类对象。
那么问题来了,在JVM加载某个类的时候,加载到某个静态方法,而该静态方法中含有this或则super的关键字,可是此时并没有任何实例化的对象存在,也就是此时还没有对象这个概念。那怎么能引用对象的成员或是引用对象的父类对象?所以静态方法中不能使用this和super关键字