有若干条Java语句组成,并且用一对大括号括起来的结构,叫做代码块。
代码块当中定义的变量是局部变量,被代码块的大括号限制作用域。
注意类体不算做代码块。
根据其位置和声明方式不同,可以分为
定义在局部位置,使用以下声明方式声明的代码块,称之为局部代码块,并且局部代码块当中还可以嵌套定义局部代码块。
// 局部位置
{
// 局部位置
{
}
// 局部位置
}
// 局部位置
局部代码块主要有两个作用:
大括号增加了代码层级,使用局部代码块显著地增加了代码的阅读难度。
指的是声明在类的成员位置,使用以下语法声明的代码块
// 成员位置
{
}
// 成员位置
随着构造器的执行而执行,和构造器的作用一样,用来在创建对象的过程中给成员变量赋值。
注:
构造器当中可以使用this关键字访问成员变量/方法,构造代码块当中也可以,构造代码块中也有this关键字,但是,构造代码块当中不能用this表示调用构造器。
总结目前为止,创建对象过程中,给成员变量赋值的手段:
- 默认初始化具有默认值(总在最先)
- 构造器赋值(总在最后)
- 显式赋值
- 构造代码块赋值
显式赋值和构造代码块赋值,是按照代码的书写顺序从上往下进行的。
为什么显示赋值和构造代码块赋值,是按照代码的书写顺序从上往下进行的
通过查看反编译文件代码,可以发现,在编译后的class文件中,是不存在构造代码块的结构的,编译器会把构造代码块中的赋值语句和显式赋值的语句,智能地加入类中的每一个构造器的前几行。
什么是“智能地”
智能是为了保证,显式赋值和构造代码块赋值按照代码的书写顺序从上到下执行。因为放入构造器中的赋值语句,在没放入构造器的赋值语句后面执行,反编译后的代码中仅存在显式赋值和构造器赋值。而构造代码块中的代码会加入构造器的前几行,是为了保证构造代码块中的语句一定在构造器之前执行。
如果构造代码块中的语句不是赋值语句,那么构造代码块中的这些代码会放入类中的每一个构造器中,这说明只要使用一次构造器new对象,那么构造代码块就会执行一次,不管用哪个构造器。
这也就决定了构造代码块最重要的特点:
无论执行哪个构造器,类中的构造代码块都会随之执行一次。这一特点决定了构造代码块的经典用途:”提取公因式“式的复用代码,将每一个构造器中都需要执行的代码,放入构造代码块中。
在创建一个对象的整个过程中,构造代码块和显式赋值的代码只会执行一次,不会执行多次
如果这个构造器的首行有this显式调用其他构造器,那么构造代码块和显式赋值的代码就不会加进去了,这是为了保证它们只被执行一次。如果它的首行没有显式调用另一个构造器,那么会先从上到下执行构造代码块和显式赋值代码,执行完毕后,跳转回构造器执行构造器代码,执行完毕后创建对象结束。
建议应该将构造代码块,放在成员变量的声明和显式赋值语句的下面。
一方面,如果构造代码块赋值放在成员变量声明的上面,逻辑上会很奇怪,降低了代码可读性。
另一方面,如果对象中成员变量的赋值,依赖于构造代码块和显式赋值的代码书写顺序,容易引发错误。
因为构造代码块最终会加入构造器,所以构造代码块当中也可以使用this关键字,指向当前对象。
声明在类的成员位置,和构造代码块只有一个区别,用static关键字声明,用以下语法定义:
// 成员位置
static{
}
// 成员位置
在类加载的过程中被执行,相当于一个在类加载过程中被自动调用的静态成员方法,用来在类加载过程中,给类的静态成员变量赋值或者做一些类的初始化工作。
注:
一般的静态成员方法是调用才会执行,并不是类加载过程中就会自动调用执行。而静态代码块是在类加载过程中自动调用的,想要一段语句能够在类加载过程中自动被调用,需要使用静态代码块,而不是静态方法。
总结目前为止,在类加载时期,给类的静态成员变量赋值的手段:
- 默认初始化,具有默认值(总在最先)
- 显式赋值
- 静态代码块赋值
显式赋值和静态代码块赋值,也是按照代码的书写顺序从上往下执行的。
静态代码块这种执行机制,是Java类加载的机制保证的,和编译器是没太大关系的,和JVM本身的类加载机制有关系。
涉及多线程知识,本文暂不详述。
在我们对类加载的简单理解中,认为:”触发类加载,完成类加载,在开始创建对象。“ 这句话在绝大多数情况下是正确的,但是这是不完善的,也有例外,比如在类加载过程中,创建类自身的静态成员变量对象,这是允许的。
类加载都没有结束,在类加载的过程中,为什么能够创建自己的对象呢?需要研究一下类加载的过程中要做什么事情。
一个类从被加载到JVM内存中开始,到卸载出内存为止,一个类的生命周期包括:
其中”加载 --> 连接 --> 初始化“这三个步骤,就是一个类加载的过程。
类加载的时机,说得更准确一点,应该是类初始化的时机。目前学习过的类初始化的时机有:
要说明在类加载过程中,创建类自身的静态成员变量对象是可以的,参考如下案例:
public class Demo {
static Demo d = new Demo();
public static void main(String[] args){
}
}
这个案例中,需要在类加载时期创建Demo类的对象。
在初始化类Demo的时候,首先需要进行加载和连接,然后进行初始化的步骤,在初始化步骤执行过程中,需要创建自身类对象。这段代码能够通过编译,不会报错,这说明:
一个容易混淆的点:
类中既有静态代码块和构造代码块时,根据静态代码块在类初始化的过程中执行,构造代码块在创建对象的过程中执行,我们可能会下意识的认为静态代码块一定在构造代码块之前进行。
但是,当类初始化的过程中创建自身类对象时,会出现例外,即在静态代码块的上面,存在静态成员变量创建自身类对象,这样就会先创建对象,在创建对象的过程中执行了构造代码块,对象创建完毕,这个静态成员变量初始化完成,才会向下执行静态代码块的代码。
所以,我们需要理解类加载的过程,根据实际情况分析执行顺序,尤其要注意在类加载中创建自身对象的这种特殊情况。
无论是构造代码块,还是静态代码块,在其中是可以给成员变量或静态成员变量赋值的。但是,定义在其中的变量都是局部变量。
绝对不能认为: 定义在构造代码块中的变量是成员变量,定义在静态代码块中的变量是静态成员变量。