【Java杂烩】继承方式下静态成员变量、普通成员变量、静态代码块、构造代码块、构造函数在JVM的加载顺序

继承方式下静态成员变量、普通成员变量、静态代码块、构造代码块、构造函数在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关键字


参考资料


  • [CSDN]-静态关键字static和静态代码块、局部代码快、构造代码块
  • [CSDN]-Java的静态变量,成员变量,静态代码块,构造块的加载顺序
  • [博客园]-关于构造代码块、局部代码块和静态代码块的作用和区别

你可能感兴趣的:(Java杂烩)