- 概述 :
- 特点 :
- 格式 :
- 情景 :
- 细节 :
- 演示 :
- 英文 :
//=v=,新版编辑器无手动添加目录的功能,PC端阅读建议通过侧边栏进行目录跳转;移动端建议用PC端阅读。😂
一、概述 :
代码块,也称为初始化块,属于类中的成员(即和属性,方法一样,是类的一部分)。代码块的形成如同方法一样,用花括号将一些代码包括起来。只不过相比方法来看,代码块只有"方法体"。
二、特点 :
与方法相比,代码块没有返回值类型,没有方法名,也没有形参列表,是个名副其实的“三无方法”。代码块只有方法体,而且在调用时,不需要通过创建对象或是像使用类方法那样显式的调用;而是在加载包含代码块的类时,或者是在实例化该类对象时,自动的隐式调用。
大家可以想象一下蛞蝓和蜗牛在形态上的区别。如下图所示:
如果说代码块就是蛞蝓[kuò yú],只有身体,没有框架;那么方法就相当于是蜗牛,既有框架来收容这个身体,身体本身的功能也正常。当然,就像蛞蝓和蜗牛都是害虫一样😂,代码块和方法也有自己的共同点,平时在方法中会写到的任意类型的语句,都可以根据需求写在代码块中。
三、格式 :
[修饰符] {
//方法体语句。(代码)
}
PS : (对格式的补充说明)
- 对于代码块前面的修饰符:可以不写!但你要是想写的话,只能使用static关键字来修饰。
- 根据代码块是否有static修饰,可以将代码块分为非静态代码块(无static修饰)和静态代码块(添加了static修饰符)。(这是本质上的区别)
- 代码块最后的分号“;”可写可不写,但是建议大家写上,以示区分。
四、情景 :
说了这么一大堆,到底啥时候要用代码块呢?
前面我们说了,代码块并不能显式调用,而是在加载类或者实例化类时隐式地调用。其实,在我们后面的使用细节中就会说到——静态代码块是随着类的加载而被隐式调用,非静态代码块则是随着类的实例化而被隐式调用。大家先了解一下,因为要给大家举个例子,所以up先提一嘴,后面我们会细说。
🆗,当多个构造器中有重复的代码片段时,我们就可以将这些重复的相同代码提取出来,放在一个非静态代码块中,这样每次创建一个该类对象,都会隐式地调用一次代码块,就不用你在每个构造器中都写一遍了,大大提高了代码的复用性。下面我们来举个例子 :
up以Rocket类(火箭类)为演示类,以Test类为测试类,我们在Rocket类中定义一个无参构造和两个带参构造,并使其都包含一段相同的代码段(模拟火箭发射的代码段)。老规矩,因为演示嘛,为了观感简洁,up将Test类写在了Rocket类的源文件下。Rocket类,Test类代码如下 :
packageknowledge.polymorphism.code_block; publicclassRocket { publicRocket() { System.out.println("火箭发射倒计时:Come on !"); System.out.println("10!"); System.out.println("9!"); System.out.println("8!"); System.out.println("7!"); System.out.println("6!"); System.out.println("5!"); System.out.println("4!"); System.out.println("3!"); System.out.println("2!"); System.out.println("1!"); System.out.println("0,发射!奥利给!"); System.out.println("---------------------"); System.out.println("火箭发射成功!(无参构造被掉用啦)"); } publicRocket(Stringname) { System.out.println(name+"火箭发射倒计时:Come on !"); System.out.println("10!"); System.out.println("9!"); System.out.println("8!"); System.out.println("7!"); System.out.println("6!"); System.out.println("5!"); System.out.println("4!"); System.out.println("3!"); System.out.println("2!"); System.out.println("1!"); System.out.println("0,发射!奥利给!"); System.out.println("---------------------"); System.out.println(name+"火箭发射成功!(第一个有参构造被调用啦)"); } publicRocket(Stringname, Stringtime) { System.out.println(name+"火箭发射倒计时:Come on !"); System.out.println("10!"); System.out.println("9!"); System.out.println("8!"); System.out.println("7!"); System.out.println("6!"); System.out.println("5!"); System.out.println("4!"); System.out.println("3!"); System.out.println("2!"); System.out.println("1!"); System.out.println("0,发射!奥利给!"); System.out.println("---------------------"); System.out.println(name+"火箭在"+time+"发射成功!(第二个有参构造被调用啦)"); } } classTest { publicstaticvoidmain(String[] args) { //利用无参构造创建火箭类对象Rocketrocket_1=newRocket(); System.out.println("============================================="); //利用第一个有参构造创建火箭类对象Rocketrocket_2=newRocket("神舟99号"); System.out.println("============================================="); //利用第二个有参构造创建火箭类对象Rocketrocket_3=newRocket("长征99号", "2077.10.1_8:34:50"); } }
运行效果如下GIF图 :
大家发现没有,三个构造器中都有表示火箭倒计时的代码,使我们的代码冗余度很高,看起来臃肿不堪,导致代码的复用性差。这时候我们可以将表示火箭倒计时的代码提取出来,放到一个非静态代码块中。这样不管你用哪个构造器来创建该类对象,只要你实例化了该类,每实例化一次都会默认隐式地调用非静态代码块中的内容。
更改后的代码如下所示 :
packageknowledge.polymorphism.code_block; publicclassRocket { { System.out.println("火箭发射倒计时:Come on !"); System.out.println("10!"); System.out.println("9!"); System.out.println("8!"); System.out.println("7!"); System.out.println("6!"); System.out.println("5!"); System.out.println("4!"); System.out.println("3!"); System.out.println("2!"); System.out.println("1!"); System.out.println("0,发射!奥利给!"); System.out.println("---------------------"); }; publicRocket() { System.out.println("火箭发射成功!(无参构造被掉用啦)"); } publicRocket(Stringname) { System.out.println(name+"火箭发射成功!(第一个有参构造被调用啦)"); } publicRocket(Stringname, Stringtime) { System.out.println(name+"火箭在"+time+"发射成功!(第二个有参构造被调用啦)"); } } classTest { publicstaticvoidmain(String[] args) { //利用无参构造创建火箭类对象Rocketrocket_1=newRocket(); System.out.println("============================================="); //利用第一个有参构造创建火箭类对象Rocketrocket_2=newRocket("神舟99号"); System.out.println("============================================="); //利用第二个有参构造创建火箭类对象Rocketrocket_3=newRocket("长征99号", "2077.10.1_8:34:50"); } }
运行结果不变,如下GIF所示 :
五、细节 :
1.静态代码块的作用是对整个类进行初始化工作,且随着类的被加载而被隐式地调用。由于一个类的字节码文件只会被加载一次,因此静态代码块也最多只能被执行一次。
而对于非静态代码块来说,每创建一个包含非静态代码块的类 (即每实例化一次该类),都会执行一次该类中的非静态代码块。注意:如果仅仅通过“类名.”的形式去调用类的静态成员(即类变量和类方法的使用),那么非静态代码块不会执行。
2.关于类的加载和类的加载时机:
up在之前的java 反射基础 万字详解中已经讲过,大家有兴趣的话可以去看看,直接在目录中找到类加载部分即可。这里up给大家把截图放过来,如下 :
3.关于在创建对象时,静态成员、非静态成员,以及构造器在一个类中的调用顺序 :
①首先执行静态代码块(因为要加载类)和静态属性的初始化;这两者的执行优先级相同,同级。但如果同时定义了多个静态代码块和多个静态属性的初始化语句,则按照定义的顺序来执行。(即谁在前头先执行谁)
②其次执行非静态代码块和非静态属性的初始化;这两者的执行优先级也相同,同级。同理,若同时定义了多个非静态代码块和多个非静态属性的初始化语句,则按照定义的先后顺序来执行。
③最后执行构造器初始化。
4.为什么是上面第三点提到的这个顺序?
我们知道,构造器的最前面默认隐含一条super(); 语句;其实,在super();语句后面,还隐含了调用非静态代码块的语句。所以,这就能解释为什么是第三点注意提到的顺序了——静态成员(静态代码块,静态属性的初始化)在类加载器加载该类时就已经执行完毕了,所以它们是第一优先级;而由于构造器中隐含了调用非静态代码块的语句,因此,必须先执行完毕非静态代码块,才能继续执行构造器。
5.在继承关系的基础上,创建子类对象时,静态成员、非静态成员,以及构造器在子父类中的调用顺序:(多层继承也适用)
①首先,执行父类的静态代码块和静态属性初始化语句。(若同时定义了多个,因为优先级同级,按照定义的先后顺序执行)
②其次,执行子类的静态代码块和静态属性初始化语句。(同上)
③接着,执行父类的非静态代码块和非静态属性初始化。(同上)
④接着,执行父类构造器。
⑤接着,执行子类的非静态代码块和非静态属性初始化。(同上)
⑥最后,执行子类构造器。
为什么?
在上文”类的加载时机“中我们谈到——初始化类的子类时,要先加载父类,而类的静态代码块和静态属性的初始化是随着类的加载而执行完毕的。因此,当创建子类对象时,第一步是执行父类的静态代码块和静态属性初始化;第二步才是执行子类的静态代码块和静态属性初始化。当子类和父类都加载成功后,jvm要根据你创建子类对象时传入的参数去找子类中对应的构造器;但是别忘了——之前我们讲到继承关系时我们说过,继承关系中,对象的初始化顺序为 : 先初始化父类内容,后初始化子类内容。所以会先通过子类的super语句,来到父类的构造器,而父类构造器又隐含了调用父类非静态代码块的语句,而且,我们在创建对象的内存图解中也讲过,对象属性的初始化有三步:默认初始化,显式初始化,构造器初始化。因此,第三步是执行父类的非静态代码块和非静态属性初始化;第四步才是执行父类的构造器。父类内容初始化完成后,返回子类构造器中,子类构造器也隐含了调用子类非静态代码块的语句,因此第五步是执行子类的非静态代码块和非静态属性初始化,第六步才是执行子类构造器。至此,一个子类对象创建完毕。
6.在讲到static关键字时我们说过,静态成员只能调用静态成员(类变量和类方法),而非静态成员既能调用静态成员,也能调用非静态成员。文章开篇我们就说了,代码块不也是类的成员么?因此,静态代码块只能调用静态成员(类变量,类方法),而非静态代码块可以调用任意成员。
六、演示 :
1.演示细节1 :
up以Cat类为演示类,以TestDetial_1作为第一个测试类。我们在Cat类中定义一个静态代码块和一个非静态代码块。通过在测试类中创建多个Cat类对象,来观察两种代码块的执行次数各有什么特点。
Cat类,TestDetail_1类代码如下 :
packageknowledge.polymorphism.code_block.chinese.detail_1; /*//细节1 :静态代码块的作用是对整个类进行初始化工作,且随着类的被加载而被隐式地调用。由于一个类的字节码文件只会被加载一次,因此静态代码块也最多只能被执行一次。而对于非静态代码块来说,每创建一个包含非静态代码块的类 (即每实例化一次该类),都会执行一次该类中的非静态代码块。注意:如果仅仅通过“类名.”的形式去调用类的静态成员(即类变量和类方法的使用),那么非静态代码块不会执行。*/publicclassCat { { System.out.println("这是Cat类中的非静态代码块捏🤗"); System.out.println("非静态代码块,随着类的实例化而执行,每实例化一次类就执行一次"); System.out.println("==============================="); }; static { System.out.println("这是Cat类中的静态代码块捏🤗"); System.out.println("静态代码块,随着类的加载而执行,仅执行一次"); System.out.println("==============================="); }; publicstaticStringname="猫"; publicstaticvoideat() { System.out.println("🐱喜欢吃🥩"); } publicCat() {} } classTestDetail_1 { publicstaticvoidmain(String[] args) { //创建Cat类对象Catcat_0=newCat(); Catcat_1=newCat(); Catcat_2=newCat(); } }
运行结果 :
2.演示细节2 :
细节2没啥演示的吧😂,基本都继承那块儿的。
这样,给大家演示一下调用静态成员时类的加载情况吧,如下 :
up以Dog类为演示类,以TestDetail_2类为第二个测试类。在Dog类中定义一个静态的dog()方法。然后在测试类中通过类名来调用这个类方法。观察该类的静态代码块是否执行。
Dog类,TestDetail_2类代码如下 :
packageknowledge.polymorphism.code_block.chinese.detail_2; publicclassDog { static { System.out.println("这是Dog类的静态代码块,如果Dog类被加载,那么这句话一定输出!"); System.out.println("------------------------------------"); }; //定义Dog类的静态方法(类方法)publicstaticvoiddog() { System.out.println("我虽然不是人,但你是真的🐕。"); } } classTestDetail_2 { publicstaticvoidmain(String[] args) { //以类名的形式调用Dog类中的类方法Dog.dog(); } }
运行结果 :
3.演示细节3 :
up以Fish类为演示类,以TestDetail_3类为第三个测试类。在Fish类中定义静态代码块,静态属性(使用静态方法给静态属性赋初值),非静态代码块,非静态属性(通过非静态方法给非静态属性赋初值),以及一个Fish类的空参构造。在每个成员中均打印出提示信息。在测试类中创建Fish类对象,观察Fish类中各个成员的执行顺序。
Fish类,TestDetail_3类代码如下 :
packageknowledge.polymorphism.code_block.chinese.detail_3; /*//细节3 :①首先执行静态代码块(因为要加载类)和静态属性的初始化;这两者的执行优先级相同,同级。但如果同时定义了多个静态代码块和多个静态属性的初始化语句,则按照定义的顺序来执行。(即谁在前头先执行谁)②其次执行非静态代码块和非静态属性的初始化;这两者的执行优先级也相同,同级。同理,若同时定义了多个非静态代码块和多个非静态属性的初始化语句,则按照定义的先后顺序来执行。③最后执行构造器初始化。*/publicclassFish { //Fish类的代码块 { System.out.println("这是Fish类的非静态代码块"); System.out.println("非静态代码块的执行处于第二优先级,因此这句话是第二阶段输出"); }; static { System.out.println("这是Fish类的静态代码块"); System.out.println("静态代码块的优先级是最高的!所以这句话必须第一阶段输出。"); }; //Fish类构造器publicFish() { System.out.println("最后输出的就是Fish类的构造器🌶!这是第三阶段输出。"); } //Fish类静态属性及其初始化publicstaticStringname=getName(); publicstaticStringgetName() { System.out.println("静态属性的初始化与静态代码块的执行为相同优先级。所以也是第一阶段输出"); System.out.println("只不过此处静态属性定义的位置靠后一点点,所以这句话输出稍微靠后。"); System.out.println("------------------------------------------------"); return"鱼"; } //Fish类非静态属性及其初始化publicStringhobby=getHobby(); publicStringgetHobby() { System.out.println("非静态属性的初始化,和非静态代码块相同优先级。"); System.out.println("只不过由定义的先后顺序来看,这句话要在非静态代码块之后才能输出。"); System.out.println("------------------------------------------------"); return"鱼能有啥爱好😅,喜欢游泳🏊?"; } } classTestDetail_3 { publicstaticvoidmain(String[] args) { //创建Fish类对象,看看各个阶段的输出内容分别是个啥。Fishfish=newFish(); System.out.println(fish.hobby); } }
运行结果 :
4.演示细节4 :
up以Fruit类为父类,子类Apple类继承了Fruit类;以TestDetail_4类为第四个测试类。在子父类中均定义一个无参构造,和一个非静态代码块。通过控制台打印出的信息观察执行规律。
Fruit类,Apple类,TestDetail_4类代码如下 :
packageknowledge.polymorphism.code_block.chinese.detail_4; /*//细节4 :构造器的最前面默认隐含一条super(); 语句;其实,在super();语句后面,还隐含了调用非静态代码块的语句。*/publicclassFruit { { System.out.println("Fruit类的非静态代码块。"); }; publicFruit() { //super(); 父类构造器中其实也隐含了supper语句,只不过顶层父类Object类中不会输出内容。//supper语句之后隐含了调用本类非静态代码块的语句。System.out.println("这是父类构造器"); } } classAppleextendsFruit { { System.out.println("Apple类的非静态代码块。"); }; publicApple() { //super();//实质这里还隐含了调用本类非静态代码块的语句。System.out.println("这是子类构造器。"); } } classTestDetail_4 { publicstaticvoidmain(String[] args) { Appleapple=newApple(); } }
运行结果 :
我们之前说过,对于非静态代码块,每实例化一次类就执行一次。
通过输出结果可以看到,尽管我们创建的是子类对象,但因为super语句调用父类构造器,而父类构造器中隐含调用非静态代码块的语句。因此最后输出的内容中,会先输出父类的非静态代码块。
5.演示细节5 :
up以Fruit类(水果类)为父类,子类Apple类(苹果类)继承Fruit类,子类的子类HongFuShi类(红富士类)继承Apple类;以TestDetail_5为第五个测试类。
在三个类中均定义静态代码块,静态属性;非静态代码块,非静态属性;以及构造器。并在每个成员中给出提示信息。在测试类中创建红富士类对象,根据控制台打印出的提示信息来
Fruit类,Apple类,HongFuShi类代码如下 :
packageknowledge.polymorphism.code_block.chinese.detail_5; publicclassFruit { //父类 : 水果类//Fruit类静态属性及其初始化privatestaticStringname=getName(); publicstaticStringgetName() { System.out.println("1.这是父类Fruit类的静态方法,用于初始化父类的静态属性。"); return"水果"; } //Fruit类的非静态属性及其初始化privateStringcolor=getColor(); publicStringgetColor() { System.out.println("7.这是父类Fruit类的非静态方法,用于初始化父类的非静态属性。"); return"水果的颜色"; } //Fruit类构造器(空参)publicFruit() { System.out.println("9.这是父类Fruit类的空参构造。"); System.out.println("-------------------------------------"); } //Fruit类的静态代码块和非静态代码块static { System.out.println("2.这是Fruit类的静态代码块"); }; { System.out.println("8.这是Fruit类的非静态代码块"); }; } classAppleextendsFruit { //子类 : 苹果类//Apple类静态属性及其初始化privatestaticStringname=getName(); publicstaticStringgetName() { System.out.println("3.这是子类Apple类的静态方法,用于初始化子类的静态属性。"); return"苹果"; } //Apple类的非静态属性及其初始化privateStringapple_color=getApple_color(); publicStringgetApple_color() { System.out.println("10.这是子类Apple类的非静态方法,用于初始化子类的非静态属性。"); return"苹果的颜色"; } //Apple类构造器(空参)publicApple() { System.out.println("12.这是子类Apple类的空参构造。"); System.out.println("-------------------------------------"); } //Apple类的静态代码块和非静态代码块static { System.out.println("4.这是Apple类的静态代码块"); }; { System.out.println("11.这是Apple类的非静态代码块"); }; } classHongFuShiextendsApple { //子类的子类 : 红富士类//HongFuShi类静态属性及其初始化privatestaticStringname=getName(); publicstaticStringgetName() { System.out.println("5.这是最底层子类HongFuShi类的静态方法,用于初始化最底层子类的静态属性。"); return"红富士"; } //HongFuShi类的非静态属性及其初始化privateStringHongFuShi_color=getHongFuShi_color(); publicStringgetHongFuShi_color() { System.out.println("13.这是最底层子类HongFuShi类的非静态方法,用于初始化最底层子类的非静态属性。"); return"红色"; } //HongFuShi类构造器(空参)publicHongFuShi() { System.out.println("15.这是最底层子类HongFuShi类的空参构造。"); System.out.println("-------------------------------------"); } //HongFuShi类的静态代码块和非静态代码块static { System.out.println("6.这是HongFuShi类的静态代码块"); System.out.println("-------------------------------------------"); }; { System.out.println("14.这是HongFuShi类的非静态代码块"); }; } classTestDetail_5 { publicstaticvoidmain(String[] args) { //创建一个红富士类对象。HongFuShihongFuShi=newHongFuShi(); } }
运行结果 :
大家一开始理解起来有点难度是正常的。大家一定要记住,每个构造器的第一行默认隐含super语句,并且在super语句之后还默认隐含了调用非静态代码块的语句。可以说,想要完全理解这一块知识点,需要大家对Java 继承有不错的掌握程度。
这里up再给大家捋一捋,如下(全文背诵) :
①首先,你要创建最底层子类红富士类的对象,肯定要先将该类的字节码文件加载到方法区,但是加载时jvm发现红富士类继承了苹果类,因此必须先加载苹果类的字节码文件;但是加载时jvm又发现苹果类继承的水果类,因此必须最先加载水果类的字节码文件。
②好嘞,我们知道,静态代码块和静态属性的初始化是随着类的加载而执行完毕的。因此,在加载水果类时,会执行水果类的静态代码块和静态属性初始化。水果类加载完毕后,就要返回去加载它的子类苹果类,因此要顺势执行苹果类中的静态代码块和静态属性初始化。等爸爸和爷爷完事儿后,这才轮到孙子,红富士类也不敢耽搁,兢兢业业赶紧加载,jvm也顺势执行红富士类中的静态代码块和静态属性初始化。到此,这三个类都加载完毕。
③这时,jvm会根据你创建红富士类对象时传入的形参列表,去红富士类中找对应的构造器。但是,刚找到构造器准备执行呢,构造器第一行埋伏着默认的super语句,立马被迫来到了苹果类;没想到丫的苹果类也隐含默认语句super,立马又被迫来到水果类;继承机制要求我们要先将父类水果类进行初始化;然而水果类的构造器第一行也有super语句,但是水果类的父类就是顶层父类Object类了,就不多说了。除去super语句外,水果类的构造器中还隐含了调用水果类非静态代码块的语句,且水果类中非静态属性的构造器初始化之前是显式初始化,因此下一步是执行水果类的非静态代码块和非静态属性初始化。这些都🆗后,才能执行水果类的构造器。好不容易算是把父类初始化完毕,这算是苹果类的super语句结束了。
④接着又返回子类苹果类的构造器中,super语句之后又隐藏了调用苹果类的非静态代码块的语句,且苹果类非静态属性的构造器初始化之前,要进行显式初始化,因此下一步是执行苹果类的非静态代码块和非静态属性初始化。这些都🆗后,才能执行苹果类的构造器。这算是最底层子类红富士类的super语句结束了。(与③同理)
⑤接着又返回最底层子类红富士类的构造器中,super语句之后又隐藏了调用红富士类的非静态代码块的语句,且红富士类非静态属性的构造器初始化之前,要进行显式初始化,因此下一步是执行红富士类的非静态代码块和非静态属性初始化。这些都🆗后,才能执行红富士类的构造器,初始化红富士类对象。至此,结束。(与③同理)
6.演示细节6 :
其实细节6也没什么好演示的,都是老生常谈的问题😂,和之前讲到static修饰方法时一个道理。
up以Student类作为演示类,以TestDetail_6作为第六个测试类。在Student类中定义静态属性name和非静态属性age;再定义静态方法getName() 和非静态方法getAge() ,并分别定义静态代码块和非静态代码块,去调用这些成员。在测试类创建Student类对象并查看输出结果。
Student类,TestDetail_6类代码如下 :
packageknowledge.polymorphism.code_block.chinese.detail_6; publicclassStudent { privatestaticStringname="Cyan"; privateintage=20; publicstaticStringgetName() { returnname; } publicintgetAge() { returnage; } { System.out.println(name); //非静态代码块——可以调用静态成员System.out.println(age); System.out.println("name = "+getName()); System.out.println("age = "+getAge()); //非静态代码块——也可以调用非静态成员 }; static { System.out.println(name); //静态代码块——只能调用静态成员//System.out.println(age); //这么写会报错。System.out.println("name = "+getName()); //System.out.println("age = " + getAge()); //静态代码块——不可以调用非静态成员System.out.println("-----------------------------------"); }; } classTestDetail_6 { publicstaticvoidmain(String[] args) { Studentstudent=newStudent(); } }
运行结果 :
如果我们在Student类的静态代码块中调用非静态成员变量age或者是非静态成员方法getAge(),IDEA就会报错,如下图所示 :
七、英文:
其实up是先发的英文版本😂,但没关系。英文版本适合英文六级及以上水平的小伙伴儿们阅读。up的英文版本都是拿英文重新描述的,所举案例和描述思想都会不同;而且up如果有写英文讲解java 的想法时,一般都是先写英文版本,再写中文版本;不会存在单纯的翻译这一“拙劣行径”,大家放心!
好滴,up把英文版本的代码块(Code Block) 详解链接给大家放下面了,感谢阅读!
https://blog.csdn.net/TYRA9/article/details/128992057?spm=1001.2014.3001.5501