Java 详细剖析了解代码块(局部代码块,构造代码块,静态代码块)及类的执行顺序

1. 简单概述代码块

程序员每天都在和代码打交道,会遇到很多成对的{ }大括号。其实,在Java中,使用{}括起来的代码被称为代码块。 你可以在类中或方法体中写很多这样空的大括号或者一些简单的输出语句,或定义等等,都不会出现编译或执行的错误。如下图所示:
在这里插入图片描述

2. 代码块的分类

根据其位置和声明的不同,可以分为:

  1. 局部代码块
  2. 构造代码块
  3. 静态代码块
  4. 同步代码块

3. 常见代码块的应用

3.1 局部代码块

在方法中出现;限定变量生命周期,及早释放,提高内存利用率

用来标识一块程序代码块,由 大括号括起来的程序片段。

这种局部代码块最常见的就是for循环语句,或while循环语句,if,else判断语句,或方法体等当中出现。

代码解释:

for(int i = 0;i < 10;i++) {
	// 这就是一段局部代码块,其中局部变量i的声明周期为本循环体内部
	// 一旦循环执行完毕,内存释放,那么变量i的声明周期也到此结束
	// 提高内存的利用率
}

3.2 构造代码块

又称为(初始化代码块)

在类中方法外出现;多个构造方法,方法中相同的代码存放到一起,每次调用构造都执行,并且在构造方法前执行

阐述:

  1. 其目的是对类进行初始化
  2. 当对象实例化后,先执行构造代码块中的内容,再执行构造器中的内容,优于构造器先执行。(一定是对象实例化后,若仅仅只是创建对象,不会调用执行)
  3. 构造代码块是给所有对象进行统一初始化,而构造函数是给对应的对象初始化。构造代码块中定义的是不同对象共性的初始化内容。

代码解释:

class Test {
	{
		// 构造代码块
		System.out.println("构造代码块");
	}
	public Test() {

	}
}

3.3 静态代码块

由 static 关键字修饰的代码块,称之为静态代码块。在上一篇博文static关键字中最后有提到。其
1. 在类中方法外出现,并加上static修饰;用于给类进行初始化,在加载的时候就执行,并且只执行一次。
2. 一般用于加载驱动

阐述:

  1. 由 static 关键字修饰,可以称之为类代码块,随着类的加载而加载,且只加载并且只执行一次,优于main函数执行。
  2. 其作用是给类进行初始化
  3. 静态代码中的变量是局部变量,也是在栈中开辟内存空间
  4. 一个类中可以由多个静态代码块

代码解释:

public class Test {
	static {
		// 静态代码块
		System.out.println("静态代码块");
	}
	public static void main(String[] args) {
	
	}
}

4. 常见的代码块的执行顺序

4.1 代码解释

// 先加载类,加载的同时会附带 static 成员分配内存,初始化,
// static 代码块执行 【进行成员的初始化】

public class Test_45_Static代码块 {
	
	// 静态代码块,随着类的加载而执行,且只执行一次,优于main函数先执行
	// 简单的来说,定义了这个类,编译执行加载类的时候,该代码块就执行
	static {
		System.out.println("我是Test_45_static代码块");
	}
	
	// 为什么 main 方法 要使用 static 修饰
	// 底层直接打点调用 main 方法
	public static void main(String[] args) {
		
		System.out.println("before");
		
		//这一行代码用到了类(即使用了类中),一定是在加载了类的前提下,所以一定会加载static代码块,但没有进行类的实例化,故不会执行构造代码块和初始化构造器
		//Student2.count =1;
		
		// 用到了类,但没有实例化,没有被加载,所以不会输出构造代码块和初始化构造器
		//Student2 s1 = null;
		
		Student2 s1 = new Student2();
		
		// 实例化对象,便会输出构造代码块和初始化构造器
		Student2 s2 = new Student2();
		
		System.out.println("after");

	}
}

/*
 * 规范的写法
 * 静态代码块中 做 static初始化
 * 额外的初始化 例如:连接数据库的初始化
 *  private static int count;
 *  
 *  static {
 *  	
 *  }
 */

class Student2 {
	private int id;
	static int count = -1;
	
	public Student2() {
		System.out.println("我是构造器");
	}
	
	// 以下代码说明,静态代码块和构造代码块的位置可以是任意的,但是习惯放在最前面
	static {
		//count = 0;
		System.out.println("我是Student2的static代码块");
		count = 0;
	}
	
	
	
	{
		System.out.println("初始化代码块1");
	}
	
//	{
//		System.out.println("初始化代码块1");
//	}
	
}

执行结果:
在这里插入图片描述

4.2 总结执行顺序

  1. 类的加载
  2. 类中的静态代码块(只执行一次)
  3. 实例化类
  4. 实例化类中的静态代码块(只执行一次)
  5. 构造代码块(实例化一次执行一次,执行n次)
  6. 构造器(实例化一次执行一次,执行n次)

5. 面试题练习

输出以下代码的执行结果:

public class Test_53_07 {
	public static void main(String[] args) {
		Y y1 = new Y();
		Y y2 = new Y();
	}
}

class X {
	static {
		System.out.println("X的静态代码块");
	}
	{
		System.out.println("X的构造代码块");
	}
	public X() {
		System.out.println("X的无参构造器");
	}
}

class Y extends X {
	static {
		System.out.println("Y的静态代码块");
	}
	{
		System.out.println("Y的构造代码块");
	}
	public Y() {
		System.out.println("Y的无参构造器");
	}
}

输出结果:
在这里插入图片描述

5.1 解析

  1. 加载Test_53_07类,将Test_53_07.class文件装入内存(方法区),形成字节码对象。
  2. 找到main函数(可以将函数名看作是一个地址值)在方法区内存中所标识的地址即代码入口,开始执行main函数,从上往下执行代码
  3. Y y1 = new Y(); 由于Y继承X,故在执行Y的构造器的时候,第一行默认super(),先初始化父类X,先将父类X完全初始化完成后,再开始执行子类Y。
  4. 由于Y对象已经实例化,肯定已经将X和Y两个类完成了加载,且二者都存在静态代码块,所以先执行父类X 的静态代码块,再执行Y类的静态代码块,输出“X的静态代码块”和“Y的静态代码块”。
  5. 接着父类X还未完全完成初始化,由于构造代码块优于构造器,故先执行父类X 的构造代码块,输出 “X的构造代码块”,在执行X的构造器,输出”X的构造器“。至此,父类完全初始化完成。
  6. 开始执行子类的初始化,执行子类的构造代码块,输出”Y的构造代码块“;执行Y的构造器,输出”Y的构造器“
  7. 至此 Y y1 = new Y();
  8. 开始执行 Y y2 = new Y();
  9. 由于静态代码块随着类的加载而加载,且只加载一次,故X和Y 的静态代码块也只执行一次。
    10.重复上述的第5和第6。

猜你喜欢

转载自blog.csdn.net/zc666ying/article/details/107772041