JavaSE_代码块

文章目录

  • 代码块的定义
  • 代码块的分类
  • 局部代码块
        • 语法
        • 作用
        • 缺点
  • 构造代码块
        • 语法
        • 作用
        • 原理
        • 注意事项
        • 使用场景
  • 静态代码块
        • 语法
        • 作用
        • 原理
        • 使用场景
        • 注意事项
  • 同步代码块
  • 类加载过程的进一步理解
        • 类的生命周期和类加载的过程
        • 类加载的时机
        • 类加载过程中创建自身类对象
        • 注意事项

代码块的定义

有若干条Java语句组成,并且用一对大括号括起来的结构,叫做代码块。

代码块当中定义的变量是局部变量,被代码块的大括号限制作用域。

注意类体不算做代码块。

 

代码块的分类

根据其位置和声明方式不同,可以分为

  1. 局部代码块:声明定义在局部位置的代码块。
  2. 构造代码块:声明定义在类的成员位置的代码块。
  3. 静态代码块:声明定义在类的成员位置的代码块,但是用static修饰。
  4. 同步代码块:是多线程中用于同步的一种代码块结构,涉及多线程知识。

 

局部代码块

语法

定义在局部位置,使用以下声明方式声明的代码块,称之为局部代码块,并且局部代码块当中还可以嵌套定义局部代码块。

// 局部位置
{
	// 局部位置
	{
	
	}
	// 局部位置
}
// 局部位置

 

作用

局部代码块主要有两个作用:

  1. 局部代码块可以用来限制局部变量的生命周期,当代码从上到下执行完该作用域后,会立即释放该局部变量,及早释放资源,提升内存利用率。
  2. 局部代码块可以用来限制局部变量的作用域,可以在一个方法中定义同名的局部变量。

 

缺点

大括号增加了代码层级,使用局部代码块显著地增加了代码的阅读难度。

 

构造代码块

语法

指的是声明在类的成员位置,使用以下语法声明的代码块

// 成员位置
{

}
// 成员位置

 

作用

随着构造器的执行而执行,和构造器的作用一样,用来在创建对象的过程中给成员变量赋值。

注:

 ​构造器当中可以使用this关键字访问成员变量/方法,构造代码块当中也可以,构造代码块中也有this关键字,但是,构造代码块当中不能用this表示调用构造器。

总结目前为止,创建对象过程中,给成员变量赋值的手段:

  1. 默认初始化具有默认值(总在最先)
  2. 构造器赋值(总在最后)
  3. 显式赋值
  4. 构造代码块赋值

显式赋值和构造代码块赋值,是按照代码的书写顺序从上往下进行的。

 

原理

为什么显示赋值和构造代码块赋值,是按照代码的书写顺序从上往下进行的

通过查看反编译文件代码,可以发现,在编译后的class文件中,是不存在构造代码块的结构的,编译器会把构造代码块中的赋值语句和显式赋值的语句,智能地加入类中的每一个构造器的前几行。

什么是“智能地”

智能是为了保证,显式赋值和构造代码块赋值按照代码的书写顺序从上到下执行。因为放入构造器中的赋值语句,在没放入构造器的赋值语句后面执行,反编译后的代码中仅存在显式赋值和构造器赋值。而构造代码块中的代码会加入构造器的前几行,是为了保证构造代码块中的语句一定在构造器之前执行。

如果构造代码块中的语句不是赋值语句,那么构造代码块中的这些代码会放入类中的每一个构造器中,这说明只要使用一次构造器new对象,那么构造代码块就会执行一次,不管用哪个构造器。

这也就决定了构造代码块最重要的特点:

无论执行哪个构造器,类中的构造代码块都会随之执行一次。这一特点决定了构造代码块的经典用途:”提取公因式“式的复用代码,将每一个构造器中都需要执行的代码,放入构造代码块中。

在创建一个对象的整个过程中,构造代码块和显式赋值的代码只会执行一次,不会执行多次

如果这个构造器的首行有this显式调用其他构造器,那么构造代码块和显式赋值的代码就不会加进去了,这是为了保证它们只被执行一次。如果它的首行没有显式调用另一个构造器,那么会先从上到下执行构造代码块和显式赋值代码,执行完毕后,跳转回构造器执行构造器代码,执行完毕后创建对象结束。

 

注意事项

  1. 建议应该将构造代码块,放在成员变量的声明和显式赋值语句的下面。

     一方面,如果构造代码块赋值放在成员变量声明的上面,逻辑上会很奇怪,降低了代码可读性。

     另一方面,如果对象中成员变量的赋值,依赖于构造代码块和显式赋值的代码书写顺序,容易引发错误。

  2. 因为构造代码块最终会加入构造器,所以构造代码块当中也可以使用this关键字,指向当前对象。

 

使用场景

  1. 构造代码块最大的特点,就是其中的代码最终会加入类的所有的构造器中,所以依据这一点:上文提到的,像”提取公因式“一样实现代码复用,把所有构造器都需要的代码,放入构造代码块中。
  2. 构造代码块毕竟是给成员变量赋值用的,所以如果需要很复杂的代码完成成员变量的赋值,使用构造代码块赋值也是一个不错的选择。
  3. 实际上,可以把构造代码块看成是一个在创建对象过程中,自动被调用的成员方法。
  4. 构造代码块可以给类的成员变量赋值,也可以给类的静态成员变量赋值,因为静态成员变量在类加载之后就存在了,而构造代码块在创建对象过程中执行,这是静态成员变量一定存在了。但是这种给静态成员变量赋值的手段和类加载完全没有关系,是依赖于创建对象过程的,那么如果需要在类加载时期给静态成员变量赋值的代码块,就用静态代码块。

 

静态代码块

语法

声明在类的成员位置,和构造代码块只有一个区别,用static关键字声明,用以下语法定义:

// 成员位置
static{

}
// 成员位置

 

作用

在类加载的过程中被执行,相当于一个在类加载过程中被自动调用的静态成员方法,用来在类加载过程中,给类的静态成员变量赋值或者做一些类的初始化工作。

注:

​ 一般的静态成员方法是调用才会执行,并不是类加载过程中就会自动调用执行。而静态代码块是在类加载过程中自动调用的,想要一段语句能够在类加载过程中自动被调用,需要使用静态代码块,而不是静态方法。

总结目前为止,在类加载时期,给类的静态成员变量赋值的手段:

  1. 默认初始化,具有默认值(总在最先)
  2. 显式赋值
  3. 静态代码块赋值

显式赋值和静态代码块赋值,也是按照代码的书写顺序从上往下执行的。

 

原理

静态代码块这种执行机制,是Java类加载的机制保证的,和编译器是没太大关系的,和JVM本身的类加载机制有关系。

 

使用场景

  1. 复杂的静态成员变量赋值可以写在静态代码块中。比如JavaEE中加载JDBC驱动。
  2. 如果有一段代码,在类的全局,从始至终只运行一次,可以写到静态代码块当中。这依赖于类加载只有一次的原理,类加载只有一次,类的静态代码块也最多执行一次,不可能执行两次。比如一些类的初始化工作,就可以放在静态代码块中完成。

 

注意事项

  1. 静态代码块可以近似看成一个,在类加载时期自动调用的静态成员方法,所以不能在静态代码块中调用非静态成员,包括this关键字和super关键字都不能使用,这是因为类加载时期没有对象。
  2. 同样因为类加载时期没有对象,虽然构造代码块可以给静态成员变量赋值,但静态代码块不能给普通成员变量赋值。
  3. 当需要使用复杂的代码给静态成员变量赋值时,可以使用静态代码块,但如果仅仅是简单的赋值,直接使用显式赋值即可。
  4. 静态代码块也经常被用来测试类加载的顺序(根据静态代码块中输出语句的输出顺序可以直观的看到不同的几个类的类加载顺序,可用于测试类加载的连环触发)。一个类的静态代码块如果没有被执行,说明它没有被完全类加载。

 

同步代码块

涉及多线程知识,本文暂不详述。

 

类加载过程的进一步理解

在我们对类加载的简单理解中,认为:”触发类加载,完成类加载,在开始创建对象。“ 这句话在绝大多数情况下是正确的,但是这是不完善的,也有例外,比如在类加载过程中,创建类自身的静态成员变量对象,这是允许的。

类加载都没有结束,在类加载的过程中,为什么能够创建自己的对象呢?需要研究一下类加载的过程中要做什么事情。

 

类的生命周期和类加载的过程

一个类从被加载到JVM内存中开始,到卸载出内存为止,一个类的生命周期包括:

JavaSE_代码块_第1张图片

其中”加载 --> 连接 --> 初始化“这三个步骤,就是一个类加载的过程。

  • 加载: 加载主要做的是将class字节码文件读取进JVM内存的操作。
  • 连接:
    1. 验证: 主要目的是为了确保class文件的字节流中包含的信息符合当前JVM的要求,不会影响JVM的安全。
    2. 准备: 主要目的是进行静态成员变量的默认初始化,设置初始值。这样就保证了静态成员变量的默认初始化永远最先进行。
    3. 解析: 主要目的是将符号引用替换为直接引用。
  • 初始化:初始化是类加载的最后一个步骤,主要目的是执行所有和static相关的内容,包括:
    1. 执行静态成员变量的显式赋值。
    2. 执行静态代码块。

 

类加载的时机

类加载的时机,说得更准确一点,应该是类初始化的时机。目前学习过的类初始化的时机有:

  1. 启动main方法
  2. new某个类的对象
  3. 访问某个类的静态成员(包括变量和方法)

 

类加载过程中创建自身类对象

要说明在类加载过程中,创建类自身的静态成员变量对象是可以的,参考如下案例:

public class Demo {
	static Demo d = new Demo();
    
	public static void main(String[] args){
    
	}
}

这个案例中,需要在类加载时期创建Demo类的对象。

在初始化类Demo的时候,首先需要进行加载和连接,然后进行初始化的步骤,在初始化步骤执行过程中,需要创建自身类对象。这段代码能够通过编译,不会报错,这说明:

  • 一个类只要经过”加载 --> 连接“,就能够创建它的自身类对象了,不需要初始化完成。
  • 某个类一旦开始进行初始化,不管有没有完成,都不会在进行第二次初始化了,这主要针对在类初始化步骤中,碰到再次需要类初始化的时机,则不会再触发第二次类初始化。

一个容易混淆的点:

类中既有静态代码块和构造代码块时,根据静态代码块在类初始化的过程中执行,构造代码块在创建对象的过程中执行,我们可能会下意识的认为静态代码块一定在构造代码块之前进行。

但是,当类初始化的过程中创建自身类对象时,会出现例外,即在静态代码块的上面,存在静态成员变量创建自身类对象,这样就会先创建对象,在创建对象的过程中执行了构造代码块,对象创建完毕,这个静态成员变量初始化完成,才会向下执行静态代码块的代码。

所以,我们需要理解类加载的过程,根据实际情况分析执行顺序,尤其要注意在类加载中创建自身对象的这种特殊情况。

 

注意事项

无论是构造代码块,还是静态代码块,在其中是可以给成员变量或静态成员变量赋值的。但是,定义在其中的变量都是局部变量

绝对不能认为: 定义在构造代码块中的变量是成员变量,定义在静态代码块中的变量是静态成员变量。
 
 

你可能感兴趣的:(JavaSE,java)