资讯动态

Java代码块全解析:四种类型与执行顺序面试指南

发布时间:2026/9/8 1:51:08 来源:尧图企业网站定制
说到 Java 代码块我估计不少刚入行的朋友第一反应是不就是一对花括号吗我第一次学 Java 的时候也是这么想的直到面试官问我“静态代码块、构造代码块和构造方法谁先执行”我当场愣住才意识到这对花括号里面的门道比想象中多得多。后来工作里看 Spring 源码、读各种框架的初始化逻辑又反复遇到静态代码块和实例初始化块的陷阱这才把这个知识点从头到尾补了一遍。这篇 Java 进阶教程的第二篇专门把代码块这个容易被忽略的知识点拿出来完整梳理。内容包括四种代码块的定义与区别、单类与继承场景下的执行顺序、实际开发里代码块适合干什么不适合干什么以及面试中最常问到的代码块问题。适合刚学完 Java 基础语法、准备进阶的读者也适合正在复习 Java 基础准备面试的朋友。如果你还没配好 Java 环境先装好 JDK 再动手跑例子别急着往下看。1. 代码块是什么先把四种形态认全1.1 四种代码块怎么区分Java 里的代码块从形态和用途上划分常见的一共有四种静态代码块、构造代码块、普通代码块、同步代码块。很多人只知道前面三种同步代码块因为跟并发锁相关往往被单独归类但从语法结构上看它同样是一对花括号包起来的代码片段所以也属于代码块的范畴。先看一个最直观的代码示例public class BlockDemo { static { // 静态代码块 System.out.println(静态代码块); } { // 构造代码块也叫做实例初始化块 System.out.println(构造代码块); } public void run() { // 普通代码块也叫局部代码块 { System.out.println(普通代码块); } // 同步代码块 synchronized (this) { System.out.println(同步代码块); } } }从位置上很好区分静态代码块在类体里用static修饰构造代码块在类体里没有任何修饰符普通代码块写在方法体内部同步代码块也是写在方法体内部但用synchronized关键字修饰。四种代码块的核心区别可以放在一张表格里看类型声明位置执行时机执行次数静态代码块类体中static修饰类加载阶段程序逻辑运行前每个类最多一次构造代码块类体中无修饰符每次new对象构造方法之前每次创建对象普通代码块方法体内执行到该位置时每次方法调用中走到那儿同步代码块方法体内synchronized常规执行 锁控制与普通代码块类似很多新手容易把构造代码块和普通代码块搞混记住一个参照点就行构造代码块在类里普通代码块在方法里。前者跟对象创建强相关后者跟作用域强相关执行时机完全不同。1.2 代码块设计出来到底图什么学代码块不能只背语法得理解它为什么存在。Java 设计代码块的核心目的是想把“类级别的初始化”和“对象级别的初始化”这两类逻辑从代码结构上区分开。静态代码块解决的是类加载时的准备工作。比如加载配置文件、注册驱动、初始化全局资源这些动作跟具体对象无关类第一次被加载时做一次就够了。构造代码块解决的是对象创建时的公共初始化逻辑。如果同一个类有多个构造方法又有一段逻辑每个构造方法都要执行拷贝粘贴会显得很蠢放进构造代码块里就能自动在各个构造方法之前执行。普通代码块的意义则完全不同。它本质上是在方法内部圈定一个临时作用域让局部变量只在特定区间内生效避免变量名污染方法内其他区域。打个比方一个类就像一栋楼静态代码块是交付前就要铺好的水电主管道构造代码块是每户装修时统一安装的插座线路普通代码块是装修工人临时搭的工作台用完就撤。2. 执行顺序是灵魂单类与继承场景逐个拆解2.1 单个类里代码块和构造方法到底谁先跑这一节是整个代码块知识的核心也是面试题最密集的地方。先看一个单类场景的完整示例public class OrderDemo { static { System.out.println(1. 静态代码块); } { System.out.println(2. 构造代码块); } public OrderDemo() { System.out.println(3. 构造方法); } public static void main(String[] args) { System.out.println(4. main开始执行); new OrderDemo(); new OrderDemo(); } }运行之后的输出结果是1. 静态代码块 4. main开始执行 2. 构造代码块 3. 构造方法 2. 构造代码块 3. 构造方法这里有两个重点值得展开说。第一静态代码块先于main执行。很多人以为类的入口是main但实际上main所在的类在调用main之前就已经被类加载器加载并初始化了所以静态代码块会抢先输出。第二构造代码块先于构造方法执行而且每次new都会执行不会因为对象构造方式不一样就跳过。再补充一个普通代码块的执行例子。普通代码块在方法体内部只有当方法被执行、程序走到花括号那里时才会运行public void printPrice() { { double price 99.9; System.out.println(旧价格: price); } { double price 88.8; System.out.println(新价格: price); } }两对花括号里都声明了price但因为各自有独立的局部作用域所以不会冲突。离开代码块后里面的变量就失效了。这也是普通代码块唯一的存在价值限制局部变量的生命周期和可见范围。当然如果你发现一个方法里的花括号多到看不清逻辑那说明该考虑抽方法了而不是继续堆代码块。2.2 继承场景下父子类的初始化顺序要背下来单类场景搞清楚了继承场景就是在其基础上叠加父类逻辑。这个知识点的面试出场率极高值得背熟。看代码class Parent { static { System.out.println(Parent static); } { System.out.println(Parent instance); } public Parent() { System.out.println(Parent constructor); } } class Child extends Parent { static { System.out.println(Child static); } { System.out.println(Child instance); } public Child() { System.out.println(Child constructor); } } public class InitOrderDemo { public static void main(String[] args) { new Child(); new Child(); } }执行结果如下Parent static Child static Parent instance Parent constructor Child instance Child constructor Parent instance Parent constructor Child instance Child constructor我可以把这个顺序压成一句口诀父静态 - 子静态 - 父实例 - 父构造 - 子实例 - 子构造。但你要明白背后的原理不能只会背。静态代码块属于类加载过程而类加载时必须先加载父类所以父类静态代码块永远在子类静态代码块之前执行。对象创建阶段子类构造方法的第一行会隐式调用super()也就是父类构造方法而父类构造方法执行前又必须先把父类的实例初始化块跑完所以父类实例相关代码排在子类前面。还有一点容易被忽略静态代码块只执行一次。上面的例子连续new了两次Child但Parent static和Child static只输出了一次而实例代码块和构造方法输出了两次。因为类加载在整个 JVM 生命周期里对同一个类加载器只发生一次静态块属于类实例块属于对象生命周期完全不同。3. 代码块的实战应用与避坑指南3.1 静态代码块的典型用途与使用禁区静态代码块在真实项目里最常见的用途有三个加载配置文件、注册驱动、初始化重量级资源。其中加载配置文件是最典型的场景以我实际写过的配置加载类为例public class AppConfig { private static final Properties PROPERTIES new Properties(); static { try (InputStream in AppConfig.class.getResourceAsStream(/app.properties)) { PROPERTIES.load(in); } catch (IOException e) { throw new ExceptionInInitializerError(e); } } public static String get(String key) { return PROPERTIES.getProperty(key); } }这个写法利用静态代码块在类加载时执行一次的特性把配置文件的读取动作收敛到一处之后任意地方调用AppConfig.get(xxx)都能直接拿到配置值不用重复读文件。但注意这里有个很重要的坑如果静态代码块内部抛出未捕获异常JVM 会把这个类标记为“初始化失败”后续代码一旦引用这个类哪怕只是访问它的静态方法甚至读取静态常量都会直接抛出NoClassDefFoundError。这个错误特别容易让人误判成“类文件丢失”实际上类文件还在只是静态初始化阶段炸了。我在项目里排查过类似问题日志里只有一行ExceptionInInitializerError或者NoClassDefFoundError真正的根因被 JVM 吞掉了非常难定位。所以静态代码块里务必使用 try-catch 把原始异常打出来或者统一包装成ExceptionInInitializerError抛出保留 cause 链否则排错排到怀疑人生。另外要记住静态代码块不适合做耗时操作。它执行在类加载阶段如果里面塞了远程调用、大数据量读取、数据库连接初始化应用启动会直接卡住。之前有个项目在静态块里拉取远程配置中心的数据网络抖动时应用启动整整等了几十秒后来改成懒加载或者放到应用启动监听器里异步处理才算解决。3.2 构造代码块和普通代码块用对地方才是好事构造代码块在实际项目里的阵地其实很窄。我见过的最合理的用法有两个一个是多个构造函数共享初始化逻辑不用在每个构造函数里重复写另一个是匿名内部类的初始化。先看多个构造函数的场景。比如一个订单类有两个构造函数一个接收订单号一个接收整个订单对象但都需要执行同样的“初始化状态”逻辑。如果不用构造代码块就得在两个构造函数里各写一遍用了构造代码块代码会清爽很多public class Order { private String orderNo; private int status; { this.status 0; System.out.println(订单状态已初始化); } public Order(String orderNo) { this.orderNo orderNo; } public Order(String orderNo, int status) { this.orderNo orderNo; this.status status; } }不过这里我必须说句实话关于这种场景Java 社区里也有不少人认为构造函数之间通过this()互调就能解决构造代码块会让代码阅读顺序变乱所以很多团队规范里明确不建议使用构造代码块。我的态度是中小规模项目里用不用都行但要知道它的存在看懂别人的代码时会少很多困惑。构造代码块还有一个隐藏的使用场景匿名内部类。匿名内部类没有构造方法但你可以通过实例初始化块来完成初始化逻辑这在写多线程代码时偶尔能看到Runnable task new Runnable() { { System.out.println(匿名内部类初始化); } Override public void run() { System.out.println(task run); } };至于普通代码块开发中最实际的作用就是隔离局部变量作用域前文已经给过例子。但如果你在一个方法里用普通代码块去“圈地”圈到后来自己都数不清第几个嵌套了我建议还是把那段逻辑抽取成独立方法。代码块的本意是让代码更清晰不是给代码添乱。3.3 顺带说一嘴的同步代码块与 Lambda同步代码块在代码块家族里的定位比较特殊它解决的是并发场景下的线程安全问题。前面说的三种代码块本质上都是“代码执行顺序”问题而同步代码块是“资源竞争”问题。典型写法是这样的private final Object lock new Object(); private int count 0; public void increase() { synchronized (lock) { count; } }用同步代码块的关键点是锁对象的选择。我见过不少人直接用字符串常量或者Integer做锁对象这非常危险。字符串常量在 JVM 里可能被多个无关的类共享Integer在值较小时会走常量池缓存一旦出现这种意外共享不同实例之间可能会互相阻塞性能直接崩掉。正确的姿势是定义一个专用的private final Object作为锁谁都不会来抢。同步代码块的粒度也要控制好。锁的范围太大比如整个方法都加上锁等于把所有线程都串行化性能损失严重锁范围太小又可能保护不到共享变量。我的经验是只锁真正操作共享资源的那几行代码其他无竞争逻辑不要放进锁里。另外搜索热词里有“lambda函数 java”这里顺带提一句Lambda 表达式里箭头后面的内容有时候也会被叫代码块但严格来说它不是语句块而是函数式接口的实现体。比如() - System.out.println(hello)后面的部分只是方法体表达式不具备独立作用域和执行时机这些代码块特征面试时不要把这两个概念混在一起讲。4. 高频面试题与排查实录4.1 面试题速答五连问代码块相关的内容在 Java 基础面试里出现频率挺高我整理了几道最常见的题目每道都附上速答思路。第一静态代码块、构造代码块、构造方法、main 的执行顺序是什么答案是静态代码块 - main - 构造代码块 - 构造方法。注意这里有个前提如果创建对象的行为发生在别的类里那么构造代码块只会出现在new之后而 main 是入口不一定跟类加载同步务必以实际加载顺序为准。第二静态代码块为什么只执行一次因为类加载过程在 JVM 中只发生一次同一个类加载器不会重复加载同一个类。静态代码块是类初始化阶段的一部分所以它绑定的是类的生命周期不是对象的生命周期。第三静态代码块能否访问实例变量和实例方法不能。静态代码块执行时对象可能还不存在没有 this 引用访问实例成员会直接编译报错。第四构造代码块的作用是什么核心作用是提供对象创建时、构造方法调用前的公共初始化逻辑还可以在匿名内部类中替代构造方法做初始化。但不要为了用而用简单类里面写构造代码块反而增加阅读负担。第五普通代码块写在方法里有什么意义限制局部变量作用域让不同代码段的临时变量互相隔离。一个方法里如果有多个独立逻辑段并且都用到了同名临时变量普通代码块可以让命名冲突消失同时让变量尽早失效便于 JVM 回收。再给一道综合题也是很多面试官喜欢考的变种。下面这段代码输出什么class A { static { System.out.print(A ); } { System.out.print(a ); } A() { System.out.print(C ); } } class B extends A { static { System.out.print(B ); } { System.out.print(b ); } B() { System.out.print(D ); } } public class Quiz { public static void main(String[] args) { new B(); new B(); } }输出结果是A B a C b D a C b D。第一次new B()触发类加载输出A B对象创建时按“父实例 - 父构造 - 子实例 - 子构造”的顺序输出a C b D。第二次new B()时静态代码块不再执行所以只剩下实例相关的两段输出。这个结果跟前面 2.2 的例子完全一致只要背熟口诀再复杂的变种也只是打印内容不同。4.2 我在实际项目中踩过的坑代码块看起来简单但实战里翻车案例一点都不少。我挑几个印象最深的分享。第一个坑是静态代码块初始化数据库连接池直接导致应用启动失败。当时项目里有个工具类静态块里自动创建连接池测试环境一切正常上线时数据库还没完全就绪应用一启动就抛NoClassDefFoundError排查了很久才发现是静态初始化炸了真正的原因是数据库连接超时。后来我把这类重量级初始化从静态块里移出去改成了应用启动后由专门的服务负责初始化并且加了完整的状态检查和重试机制问题才彻底解决。这条经验让我养成了一个习惯凡是在静态块里做任何可能会失败的操作都必须把异常处理写完整别指望 JVM 给你兜底。第二个坑是构造代码块里访问尚未声明的字段直接编译失败。Java 对实例字段的前向引用有严格限制在字段声明之前通过简单名称读取该字段会报illegal forward reference。比如这样写public class MyClass { { System.out.println(name); } private String name think; }编译时直接报错。原因是 Java 要靠固定的初始化顺序来保证代码可预测性既然字段初始化还没有执行你就在初始化块里读取它那读到的是默认值还是赋值后的值谁也说不清楚。解决办法很简单要么把字段声明挪到代码块前面要么不要在代码块里直接引用这个字段。第三个坑是方法里滥用普通代码块。我接手过一个老模块一个方法四百多行里面有十几个孤立花括号每段用花括号圈起来变量作用域确实被隔离了但代码阅读体验极差往下滚动时完全分不清哪个括号配哪个。这种用法的初心可以理解但正确做法是抽方法让逻辑边界变成方法边界而不是靠花括号硬隔。后来我重构的时候把每个花括号块都提取成了职责明确的私有方法代码可读性好了一个量级。第四个坑相对隐蔽跟同步代码块的锁对象有关。当时线上有一个并发计数器有人在里面用了synchronized (Integer.valueOf(count))这种写法导致多个线程在count值相同时互相阻塞整体性能直线下降而且问题在低并发下完全看不出来。排查到最后发现是自动装箱导致的Integer缓存共享问题。从那以后我对锁对象的选择极其敏感一律使用独立的private final Object lock不搞任何花活。写在最后的实在话代码块这个知识点单独拎出来说内容不算多但它跟类加载机制、对象创建流程、并发锁控制都有交集属于典型的“小切口、大关联”题目。很多人觉得代码块不过是语法糖不值得单独研究可一旦遇到静态初始化失败、对象初始化顺序依赖这类问题没有这块知识打底排查起来会非常被动。我个人建议你把 2.1 和 2.2 的示例代码亲手跑一遍跑完再把 static、实例块、构造方法三者的执行顺序写在自己的笔记里不用背写几次自然就记住了。之后再去看一些框架源码比如 Spring 里的初始化逻辑会发现很多设计其实都是围绕着“类加载一次、对象每次创建”这两条线展开的代码块正好是观察这两条线的绝佳窗口。如果能顺着这个思路去理解代码块这道题才算真正吃透了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价