1. 代码块的基本介绍
代码块又称初始化块,属于类中的成员【是类的一部分】,类似于方法,将逻辑语句封装在方法体中,通过 {}
包围起来
但和方法不同,代码块没有方法名,也没有返回值和参数列表,仅仅只有一个方法体。代码块不能通过对象或者类去显示调用,而是在加载类时、或者创建对象时隐式调用。
2. 代码块的基本语法
[修饰符] {
逻辑语句;
};
注意:
(1) 修饰符可写可不写,若要写,仅仅能写 static
(2) 代码块可分为两类,使用 static
修饰的叫做静态代码块;没有 static
修饰的,叫普通代码块/非静态代码块
(3) 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
(4) ;
号可以省略
3. 使用代码块的好处
(1) 代码块相当于另一种形式的构造器,是对构造器的补充机制,可以做初始化的操作
(2) 如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
下面通过一个案例,来展现代码块的一个使用场景:
class Movie {
private String name;
private double price;
private String director;
// 以下三个构造器都有相同的语句
// 这样代码看起来会比较冗余
public Movie() {
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正式开始...");
}
public Movie(String name) {
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正式开始...");
this.name = name;
}
public Moive(String name, double price) {
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正式开始...");
this.name = name;
this.price = price;
}
public Movie(String name, double price, String director) {
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正式开始...");
this.name = name;
this.price = price;
this.director = director;
}
}
这时,我们可以将构造器中相同的部分,放入一个代码块中:
class Movie {
private String name;
private double price;
private String director;
// 使用代码块
// 这样不管我们调用哪个构造器去创建对象,都会先调用代码块中的内容
// 代码块调用的顺序优先于构造器
{
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正式开始...");
}
public Movie() {
}
public Movie(String name) {
this.name = name;
}
public Moive(String name, double price) {
this.name = name;
this.price = price;
}
public Movie(String name, double price, String director) {
this.name = name;
this.price = price;
this.director = director;
}
}
4. 使用代码块的细节
static
代码块也叫静态代码块,作用是对类进行初始化,而且它随着 类的加载 而执行,并且仅仅只会执行一次;但如果是普通代码块,每次使用 new
去创建一个对象,就会执行,可以这样记忆:普通代码块只有构造器被调用了才会调用,与类是否被加载无关。
类什么时候会被加载【重点】
(1) 使用 new
创建对象实例时
(2) 创建子类对象实例时,其父类会先被加载
(3) 使用类的某一个静态成员时(静态属性、静态方法),该类也会被加载
普通代码块在创建对象实例时,会被隐式的调用,每被创建一次,就会被调用一次,跟类是否被加载无关;
但是如果是仅仅使用类的某个静态成员时,普通代码块并不会被执行,但是由于使用类的静态成员会引起类的加载,在加载时会调用该类的 static
代码块。
演示:因为加载类时,会先加载父类,再加载子类,因此父类的静态代码块会相较于子类的静态代码块先输出:
class Animal {
static {
System.out.println("Animal 的静态代码块被执行..."); // (1)
}
}
class Cat extends Animal {
public static int num = 999; // 静态属性
// 普通代码块,在new 对象被调用时,才会被执行
{
System.out.println("Cat 的普通代码块被执行..."); // 调用静态成员时,类的普通代码块不执行
}
static {
System.out.println("Cat 的静态代码块被执行..."); // (2)
}
}
public class Main {
public static void main(String[] args) {
// 输出顺序见注释
System.out.println(Cat.num); // (3)
}
}
创建一个对象时,在一个类内部调用的顺序【重点、难点】
调用顺序如下:
(1) 调用静态代码块和静态属性的初始化;
注意:静态代码块和静态属性初始化调用的优先级一致,如果有多个静态代码块和多个静态变量初始化,则按照他们定义的顺序依次调用。
(2) 调用普通代码块和普通属性的初始化;
注意:普通代码块和普通属性初始化调用的优先级一致,如果有多个普通代码块和多个普通属性初始化,则按照他们定义的顺序依次调用。
(3) 调用类的构造方法。
练习:
class A {
// 这里使用方法来进行属性初始化,完全是为了打印调用顺序演示
private static n1 = getVal();
private int n2 = getVal2();
{
System.out.println("A的普通代码块被调用..."); // (4)
}
static {
System.out.println("A的静态代码块被调用..."); // (2)
}
public A() {
// 隐藏:调用 super();
// 隐藏:调用 {} 普通方法块和普通属性初始化
System.out.println("A的构造器被调用..."); // (5)
}
public static int getVal() {
System.out.println("getVal() 方法被执行..."); // (1)
return 10;
}
public int getVal2() {
System.out.println("getVal2() 方法被执行..."); // (3)
return 30;
}
}
public class Main {
public static void main(String[] args) {
new A(); // 打印顺序参考备注
}
}
由上面的例题中的注释:每一个类的构造器(构造方法)的方法体中,其实隐含了 super()
和 调用普通代码块和普通属性初始化
。
class A {
// (1) super();
// (2) 调用普通代码块和普通属性初始化;
(3) 构造器自己的方法…
};
当包含继承关系,创建子类时代码块、构造器的调用顺序【重点、难点、面试题常考】
调用顺序如下:
(1) 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
(2) 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
(3) 父类的普通代码块和普通属性初始化(优先级一样,按照定义顺序执行)
(4) 父类的构造方法
(5) 子类的普通代码块和普通属性初始化(优先级一样,按照定义的顺序执行)
(5) 子类的构造方法
例题:
class A {
private static int n1 = getVal01();
static {
System.out.println("A 的一个静态代码块..."); // (2)
}
{
System.out.println("A 的一个普通代码块..."); // (5)
}
public int n2 = getVal02();
public static int getVal01() {
System.out.println("A 的静态属性初始化..."); // (1)
return 10;
}
public int getVal02() {
System.out.println("A 的普通属性初始化..."); // (6)
return 10;
}
public A() {
// super()
// {} 和 普通属性初始化
System.out.println("A 的构造器..."); // (7)
}
}
class B extends A {
private static int n3 = getVal03();
static {
System.out.println("B 的一个静态方法块..."); // (4)
}
public int n4 = getVal04();
{
System.out.println("B 的一个普通代码块..."); // (9)
}
public static int getVal03() {
System.out.println("B 的静态属性初始化..."); // (3)
return 10;
}
public int getVal04() {
System.out.println("B 的普通属性初始化..."); // (8)
return 10;
}
public B() {
// super()
// {} 和 普通属性初始化
System.out.println("B 的构造器..."); // (10)
}
}
public class Main {
public static void main(String[] args) {
new B(); // 备注中显示顺序
}
}
运行结果如下图所示:
此外,还需要注意的是:
静态代码块和静态方法一致,只能使用静态属性和静态方法。
普通代码块和普通方法一致,不仅可以使用普通属性和普通方法,也可以使用静态方法和静态属性。