什么叫代码块(Code Block)?用大括号把多行代码封装在一起,形成一个独立的数据
体,实现特定算法的代码**即为代码块,一般来说代码块是不能单独运行的,必须要有运
行主体。在 Java 中一共有四种类型的代码块:
(1)普通代码块
就是在方法后面使用“{}”括起来的代码片段,它不能单独执行,必须通过方法名调用
执行。
(2)静态代码块
在类中使用 static 修饰,并使用“{}”括起来的代码片段,用于静态变量的初始化或对
象创建前的环境初始化。
(3)同步代码块
使用 synchronized 关键字修饰,并使用“{}”括起来的代码片段,它表示同一时间只能
有一个线程进入到该方法块中,是一种多线程保护机制。
(4)构造代码块
在类中没有任何的前缀或后缀,并使用“{}”括起来的代码片段。
我们知道,一个类至少有一个构造函数(如果没有,编译器会无私地为其创建一个无参
构造函数) ,构造函数是在对象生成时调用的,那现在的问题来了 :构造函数和构造代码块
是什么关系?构造代码块是在什么时候执行的?在回答这个问题之前,我们先来看看编译器
是如何处理构造代码块的,看如下代码:
public class Client {
{
// 构造代码块
System.out.println("执行构造代码块");
}
public Client(){
System.out.println("执行无参构造 ");
}
public Client(String _str){
System.out.println("执行有参构造 ");
}
}
这是一段非常简单的代码,它包含了构造代码块、无参构造、有参构造,我们知道代码
块不具有独立执行的能力,那么编译器是如何处理构造代码块呢?很简单,编译器会把构造
代码块插入到每个构造函数的最前端,上面的代码与如下代码等价:
public class Client {
public Client(){
System.out.println("执行构造代码块");
System.out.println("执行无参构造");
}
public Client(String _str){
System.out.println("执行构造代码块");
System.out.println("执行有参构造");
}
}
每个构造函数的最前端都**入了构造代码块,很显然,在通过 new 关键字生成一个实
例时会先执行构造代码块,然后再执行其他代码,也就是说 :构造代码块会在每个构造函数
内首先执行(需要注意的是 :构造代码块不是在构造函数之前运行的,它依托于构造函数的
执行) ,明白了这一点,我们就可以把构造代码块应用到如下场景中:
(1)初始化实例变量(Instance Variable)
如果每个构造函数都要初始化变量,可以通过构造代码块来实现。当然也可以通过定义
一个方法,然后在每个构造函数中调用该方法来实现,没错,可以解决,但是要在每个构造
函数中都调用该方法,而这就是其缺点,若采用构造代码块的方式则不用定义和调用,会直
接由编译器写入到每个构造函数中,这才是解决此类问题的绝佳方式。
(2)初始化实例环境
一个对象必须在适当的场景下才能存在,如果没有适当的场景,则就需要在创建对象时
创建此场景,例如在 JEE 开发中,要产生 HTTP Request 必须首先建立 HTTP Session,在创建 HTTP Request 时就可以通过构造代码块来检查 HTTP Session 是否已经存在,不存在则创建之。
以上两个场景利用了构造代码块的两个特性 :在每个构造函数中都运行和在构造函数中
它会首先运行。很好地利用构造代码块的这两个特性不仅可以减少代码量,还可以让程序更
容易阅读,特别是当所有的构造函数都要实现逻辑,而且这部分逻辑又很复杂时,这时就可
以通过编写多个构造代码块来实现。每个代码块完成不同的业务逻辑(当然了,构造函数尽
量简单,这是基本原则) ,按照业务顺序依次存放,这样在创建实例对象时 JVM 也就会按照顺序依次执行,实现复杂对象的模块化创建。
建议37:如果在一个类的构造器中使用this调用该类的另一个构造器
编译器会把构造代码块插入到每一个构造函数中,但是有一个例外的情况没有说明 :如果遇到 this 关键字(也就是构造函数调用自身其他的构造函数时)则不插入构造代码块
还有一点需要说明,读者千万不要以为 this 是特殊情况,那 super 也会类似处理了。其实不会,在构造代码块的处理上,super 方法没有任何特殊的地方,编译器只是把构造代码块插入到 super 方法之后执行而已,仅此不同。
体,实现特定算法的代码**即为代码块,一般来说代码块是不能单独运行的,必须要有运
行主体。在 Java 中一共有四种类型的代码块:
(1)普通代码块
就是在方法后面使用“{}”括起来的代码片段,它不能单独执行,必须通过方法名调用
执行。
(2)静态代码块
在类中使用 static 修饰,并使用“{}”括起来的代码片段,用于静态变量的初始化或对
象创建前的环境初始化。
(3)同步代码块
使用 synchronized 关键字修饰,并使用“{}”括起来的代码片段,它表示同一时间只能
有一个线程进入到该方法块中,是一种多线程保护机制。
(4)构造代码块
在类中没有任何的前缀或后缀,并使用“{}”括起来的代码片段。
我们知道,一个类至少有一个构造函数(如果没有,编译器会无私地为其创建一个无参
构造函数) ,构造函数是在对象生成时调用的,那现在的问题来了 :构造函数和构造代码块
是什么关系?构造代码块是在什么时候执行的?在回答这个问题之前,我们先来看看编译器
是如何处理构造代码块的,看如下代码:
public class Client {
{
// 构造代码块
System.out.println("执行构造代码块");
}
public Client(){
System.out.println("执行无参构造 ");
}
public Client(String _str){
System.out.println("执行有参构造 ");
}
}
这是一段非常简单的代码,它包含了构造代码块、无参构造、有参构造,我们知道代码
块不具有独立执行的能力,那么编译器是如何处理构造代码块呢?很简单,编译器会把构造
代码块插入到每个构造函数的最前端,上面的代码与如下代码等价:
public class Client {
public Client(){
System.out.println("执行构造代码块");
System.out.println("执行无参构造");
}
public Client(String _str){
System.out.println("执行构造代码块");
System.out.println("执行有参构造");
}
}
每个构造函数的最前端都**入了构造代码块,很显然,在通过 new 关键字生成一个实
例时会先执行构造代码块,然后再执行其他代码,也就是说 :构造代码块会在每个构造函数
内首先执行(需要注意的是 :构造代码块不是在构造函数之前运行的,它依托于构造函数的
执行) ,明白了这一点,我们就可以把构造代码块应用到如下场景中:
(1)初始化实例变量(Instance Variable)
如果每个构造函数都要初始化变量,可以通过构造代码块来实现。当然也可以通过定义
一个方法,然后在每个构造函数中调用该方法来实现,没错,可以解决,但是要在每个构造
函数中都调用该方法,而这就是其缺点,若采用构造代码块的方式则不用定义和调用,会直
接由编译器写入到每个构造函数中,这才是解决此类问题的绝佳方式。
(2)初始化实例环境
一个对象必须在适当的场景下才能存在,如果没有适当的场景,则就需要在创建对象时
创建此场景,例如在 JEE 开发中,要产生 HTTP Request 必须首先建立 HTTP Session,在创建 HTTP Request 时就可以通过构造代码块来检查 HTTP Session 是否已经存在,不存在则创建之。
以上两个场景利用了构造代码块的两个特性 :在每个构造函数中都运行和在构造函数中
它会首先运行。很好地利用构造代码块的这两个特性不仅可以减少代码量,还可以让程序更
容易阅读,特别是当所有的构造函数都要实现逻辑,而且这部分逻辑又很复杂时,这时就可
以通过编写多个构造代码块来实现。每个代码块完成不同的业务逻辑(当然了,构造函数尽
量简单,这是基本原则) ,按照业务顺序依次存放,这样在创建实例对象时 JVM 也就会按照顺序依次执行,实现复杂对象的模块化创建。
建议37:如果在一个类的构造器中使用this调用该类的另一个构造器
编译器会把构造代码块插入到每一个构造函数中,但是有一个例外的情况没有说明 :如果遇到 this 关键字(也就是构造函数调用自身其他的构造函数时)则不插入构造代码块
还有一点需要说明,读者千万不要以为 this 是特殊情况,那 super 也会类似处理了。其实不会,在构造代码块的处理上,super 方法没有任何特殊的地方,编译器只是把构造代码块插入到 super 方法之后执行而已,仅此不同。