☕ 第三章 面向对象(上)

3.6 代码块

📄 一句话概括

用 { } 包起来的一段代码就是代码块。按位置分三种:局部代码块(方法里,限制变量寿命)、构造代码块(方法外,每次 new 都跑)、静态代码块(static { },全程序只跑一次)——考试常考它们的执行顺序。


1. 代码块是什么——你其实早就在写

从 1.3 第一行代码起你就天天和 { } 打交道:类有 { }、方法有 { }、if 和 for 有 { }。一对花括号包起来的独立区域,就叫代码块。

花括号有个天生的规矩:它是变量作用的边界(2.2 学的作用域)——变量在哪个 { } 里定义,出这个 { } 就消失。

本节要学的不是这个基本盘,而是三种"独立存在"的代码块——它们不在任何方法里(或正是为了管住方法里的变量),各有各的出场时机。


2. 局部代码块——方法里的"临时车间"

在方法内部单独写一对 { }:

public static void main(String[] args) {
    System.out.println("开始");

    {   // 局部代码块
        int temp = 999;
        System.out.println("临时计算:" + temp * 2);
    }   // temp 在这里消亡

    // System.out.println(temp);   // 编译报错:找不到符号 temp
    System.out.println("结束");
}

作用就一条:给临时变量划个短命区——大段逻辑中某几个变量只在中间步骤有用,装进局部代码块,出块即释放,避免名字污染整个方法。

💡 实话实说

局部代码块日常用得不多(一个方法本来就该短小,需要隔离时通常直接拆成新方法)。但它揭示了核心规则:{ } 就是变量世界的围墙——这条规则贯穿后面两种代码块。


3. 构造代码块——每次 new 的"开场白"

写在类里、方法外的 { }:

public class Car {
    private String brand;
    private double price;

    {   // 构造代码块:不写 static,光秃秃一对 { }
        System.out.println("一辆新车进入装配线");
    }

    public Car() {
        System.out.println("无参构造完成");
    }

    public Car(String brand, double price) {
        this.brand = brand;
        this.price = price;
        System.out.println("带参构造完成");
    }
}

new 两次的输出:

一辆新车进入装配线    ← 构造代码块
无参构造完成          ← 构造方法
一辆新车进入装配线    ← 又是构造代码块(每次 new 都跑)
带参构造完成

两条规律:

  1. 每次 new 都执行,无一例外
  2. 总是先于构造方法——所以它常被叫作"所有构造方法的开场白"

用途:多个构造方法里有公共的初始化动作时,抽到构造代码块里,只写一遍。比如"每次造车都要贴出厂编号"这种所有出生方式都做的事。

💡 和 3.5 的关系

3.5 的 this(...) 构造互调也能收拢公共逻辑(转发到全参构造)。构造代码块是更老的办法——不管调哪个构造都自动跑,连转发都省了。现代代码里它用得不算多,但读别人代码、应付考试必须认识。


4. 静态代码块——类的"开机自检"

在构造代码块前加 static:

public class Car {
    static {
        System.out.println("Car 类加载完毕,开机自检");
    }
}

它的出场时机和前两种完全不同:不属于任何对象,属于类本身——在类第一次被使用(第一次 new、第一次访问它的成员)时执行,全程序从头到尾只跑一次。

public static void main(String[] args) {
    System.out.println("main 开始");
    new Car();
    new Car();
    System.out.println("main 结束");
}

输出:

main 开始
Car 类加载完毕,开机自检    ← 第一次 new 前才执行,且只有这一次
(构造代码块、构造方法的输出省略)
main 结束
💡 static 先混个脸熟

static 意思是"属于类的、大家共用的"——所以静态代码块跟着类走而不是跟着对象走:类一辈子加载一次,它就一辈子跑一次。static 的完整面貌(静态属性、静态方法、为什么 main 是 static)是后面专门的一节,这里只需记住:静态代码块 = 类的开机自检,仅一次。

真实用途举例:Java 程序连数据库前要先"加载驱动",这种一次性准备工作就放在静态代码块里——保证要用前一定就绪,且绝不做第二遍。


5. 执行顺序——本节的考试中心

三种块凑齐时的完整顺序(上面那张图):

第一次用到类:
  ① 静态代码块          (仅这一次,跟类走)
每次 new 时:
  ② 构造代码块          (每次都跑,先于构造方法)
  ③ 构造方法            (收尾,真正装值)

口诀:静态一次 → 构造块每次 → 构造方法收尾。

同一种块有多个时(不推荐但合法),按书写的先后顺序执行。


6. 动手部分:亲眼看一遍执行顺序

第 1 步:在 IDEA02 里新建类 Game:

public class Game {
    static {
        System.out.println("① 静态代码块:加载游戏资源");
    }

    {
        System.out.println("② 构造代码块:创建对局环境");
    }

    public Game() {
        System.out.println("③ 无参构造:开局");
    }

    public Game(String mode) {
        System.out.println("③ 带参构造:开局(模式 " + mode + ")");
    }
}

第 2 步:新建测试类 TestGame:

public class TestGame {
    public static void main(String[] args) {
        System.out.println("main 开始");
        Game g1 = new Game();
        Game g2 = new Game("排位");
        System.out.println("main 结束");
    }
}

看到什么算成功:控制台共 7 行,顺序必须是——

main 开始
① 静态代码块:加载游戏资源
② 构造代码块:创建对局环境
③ 无参构造:开局
② 构造代码块:创建对局环境
③ 带参构造:开局(模式 排位)
main 结束

① 只出现一次(第一次 new 前才触发),② 每次都出现且总在 ③ 前面——这三条规律亲眼看一遍,比背十遍口诀都牢。

三个 30 秒实验:

  1. 在 TestGame.main 里不 new,只写 System.out.println(Game.class.getName());——静态代码块也会跑!因为它访问了 Game 类(触发类加载),静态块跟类走,不跟对象走
  2. 把构造代码块里的打印挪到构造方法后面书写(位置在类的下方)——输出不变,顺序由种类决定,与书写位置无关(静态块同理)
  3. 在 Game 里写两个静态代码块——按书写顺序各跑一次,总共各一遍

7. 常见误区

⚠️ 误区 1:构造代码块在构造方法"后面"执行,因为写在下面

顺序由种类决定(静态 → 构造块 → 构造方法),与书写位置无关。写在类末尾的构造代码块照样先于写在类开头的构造方法执行。

⚠️ 误区 2:静态代码块每次 new 都会跑

恰恰相反——它跟类走不跟对象走,类一辈子只加载一次,它就只跑一次。第二次、第三次 new 时它装聋作哑。跑一次的是构造代码块。

⚠️ 误区 3:静态代码块在 main 方法之前执行

大多数情况下你先 new 了才看到它跑,容易以为它在 main 前执行。实际触发时机是"类第一次被使用"——main 里如果不碰这个类,静态块根本不执行(实验 1 验证了这点)。顺序上它一定早于该类的任何对象出生,但不一定早于 main 开始。

⚠️ 误区 4:局部代码块里的变量出块后还在

出 { } 即消亡,块外访问直接编译报错"找不到符号"。这正是它存在的意义:给临时变量划短命区。

⚠️ 误区 5:三种代码块都能访问对方定义的变量

不能。各自的 { } 都是独立的围墙:局部块的变量出块即死;构造块/静态块里的变量也只在自己块内有效(静态块连实例属性都碰不了——它执行时对象还不存在,这是 static 的一课,后面收网)。


8. 自测题

❓ Q1:三种代码块分别写在哪里?各自的执行时机是什么?

局部代码块在方法里,执行到就跑(变量出块即死);构造代码块在类里方法外,每次 new 都跑且先于构造方法;静态代码块加 static,类第一次被使用时跑,全程序仅一次。

❓ Q2:new 一个类三次,静态块、构造块、构造方法各执行几次?

静态块 1 次(第一次 new 前触发类加载)、构造块 3 次、构造方法 3 次。口诀:静态一次 → 构造块每次 → 构造方法收尾。

❓ Q3:构造代码块写在类的最后,执行时会晚于构造方法吗?

不会。执行顺序由块的种类决定,与书写位置无关——构造代码块永远先于构造方法。它就是"所有构造方法的开场白",写在哪儿都改不了身份。

❓ Q4:静态代码块和构造代码块用途上的最大区别是什么?各举一个典型场景。

静态块做类级别的一次性准备(如加载数据库驱动——全程序做一次就够);构造块做每个对象的公共初始化(如每辆车都贴出厂编号——每 new 一次都要做)。一个跟着类走、一次;一个跟着对象走、每次。

❓ Q5(挑战):main 方法里完全不 new Game 类,静态代码块会执行吗?什么条件下会?

不 new 也可能执行——只要 Game 类第一次被使用就触发类加载。比如访问它的静态成员、用 Game.class 反射拿类信息,都会让静态块跑起来。它绑定的单位是"类",不是"对象"。


9. 下一步学什么


相关笔记