一、调试究竟在做什么
调试(Debugging)就是查找并改正程序错误的过程。 核心是观察程序的实际执行过程,逐步缩小问题范围,找到“从哪一步开始,实际结果与预期不一致”。
常见方式有两种:
-
阅读代码、添加打印语句:代码较少时,按执行顺序检查,或用
System.out.println()输出中间结果。 - 使用调试器:让程序在指定位置停下来,查看变量,再控制它一步步执行。代码较复杂、需要反复观察时更方便。
以下使用 IntelliJ IDEA,Java 环境沿用课件中的 JDK 17。快捷键按课件的 Windows 键位说明;若自行修改过键位,以按钮提示为准。
二、先让程序在断点处停下来
1. 准备示例
新建 Test.java,后面的操作都围绕这段代码展开:
public class Test {
public static void main(String[] args) {
int a = 10;
int b = 20;
int ret = add(a, b); // 断点 A
System.out.println(ret); // 断点 B,后面再添加
}
public static int add(int x, int y) {
System.out.println("进入 add");
return x + y;
}
}
正常运行时输出:
进入 add
30
2. 设置断点并启动 Debug
断点是告诉调试器“执行到这里先停一下”的标记。
- 在
int ret = add(a, b);所在行的行号旁边单击,出现红点,即设置了断点 A。 - 在编辑区右键,选择
Debug 'Test.main()'。 - 程序运行到断点 A 时暂停,下方出现 Debug 窗口。
再次单击红点可以删除该断点。要让断点起作用,应使用 Debug 启动。
3. 看懂“停在这一行”
刚命中行断点时,该行通常还没有执行。
本例停在断点 A 时:
-
a = 10、b = 20已执行,可以查看它们的值。 -
add(a, b)尚未执行,ret还没有完成初始化。 - 控制台还没有输出“进入 add”和
30。
红点表示断点位置,执行高亮表示程序当前停留的位置。判断某个结果为什么还没出现,先检查产生结果的语句是否已经执行。
三、暂停后先看什么
1. Debugger:看变量和调用堆栈
变量区显示当前选中方法中的变量及其值。例如,停在 main 中可以查看 a、b;进入 add 后,可以查看 x、y。
调用堆栈表示当前有哪些方法正在调用、等待返回。例如:
add:行号
main:行号
表示 main 调用了 add,当前正在执行 add,main 正在等待它返回。最上方通常是当前执行的方法,下面是调用它的方法。
点击不同的方法记录,可以查看该次调用对应的变量。后面的数字是代码行号,不是调用次数。
2. 表达式求值与监视
程序暂停时,在 Debugger 中的 Evaluate expression ... or add a watch 输入区域填写表达式,按回车可查看结果。
例如停在 add 方法内部、x = 10、y = 20 时:
x + y // 结果:30
(x + y) * 2 // 结果:60
- 求值:临时计算一次,检查当前结果。
- 监视(Watch):把表达式加入监视列表,后续暂停时方便反复查看。
表达式中的变量必须在当前查看的位置可用。例如选中 add 时使用 x、y,选中 main 时使用 a、b。
3. Console:看输入输出
Console 显示控制台的输入输出,Debugger 用来观察执行状态。
只有输出语句执行之后,Console 才会出现对应内容。程序停在 System.out.println(ret); 这一行时,并不表示 30 已经打印。
四、用单步操作控制执行过程
1. 先区分四个操作
| 操作 | 快捷键 | 作用 |
|---|---|---|
| Step Over(步过/逐过程) | F8 |
执行当前行;遇到方法调用时,通常不进入方法内部逐步查看 |
| Step Into(步入/逐语句) | F7 |
进入当前行调用的方法,查看内部执行过程 |
| Force Step Into(强制步入) | Alt + Shift + F7 |
进入普通步入可能因过滤规则而跳过的方法 |
| Step Out(步出/跳出) | Shift + F8 |
执行完当前方法剩余部分,返回调用它的位置 |
2. 用同一段代码理解 F8、F7 和 Shift + F8
情况一:只关心方法的返回结果,用 F8。
停在断点 A 时按 F8,add(a, b) 会正常执行。到达下一行 System.out.println(ret); 时:
-
ret = 30。 - Console 已输出“进入 add”。
-
30还没输出,再执行当前行才会输出。
步过只是通常不进入方法内部逐步查看,方法本身仍然执行。 如果方法内部另有断点,也可能在内部停下来。
情况二:想检查方法内部的计算过程,用 F7。
重新调试,在断点 A 按 F7:
- 进入
add,停在System.out.println("进入 add");。 - 变量区可以看到
x = 10、y = 20。 - 按
F8,执行打印语句,停在return x + y;。 - 此时可以求值
x + y,确认结果为30。 - 继续执行返回语句,返回
main;调用及赋值完成后,ret才得到30。
情况三:已经进入方法,但不想继续逐行看,用 Shift + F8。
如果刚进入 add 就使用步出,剩余的打印和 return 仍会执行,然后回到 main 的调用位置。
步出不会取消这次调用,也不会丢掉剩余代码。 返回后界面可能仍高亮调用所在行,继续单步即可观察后续赋值和输出。
3. 强制步入用在什么地方
普通 F7 可能按调试器的过滤规则跳过某些 Java 类库方法,例如 System.out.println() 的内部实现。确实需要查看这些方法时,可以使用 Alt + Shift + F7。
“强制”是指绕过普通步入的过滤规则,并不意味着任何方法都一定有可供查看的源码。本章只需掌握它与普通步入的区别。
上述步进操作的含义可对照 IDEA 官方说明。
五、不想逐行执行时怎么做
1. 继续运行到下一个断点:F9
在 System.out.println(ret); 这一行再设置断点 B。
程序停在断点 A 时,按 F9(Resume Program),程序继续正常执行,命中断点 B 后暂停。此时 ret = 30,但 30 尚未打印。
如果后续没有命中其他断点,程序就会继续运行,直到结束或因其他原因暂停。
“到下一个断点”按实际执行顺序判断,不是按编辑器中红点的上下顺序判断。
2. 运行到光标处:Alt + F9
只想临时运行到某一行,可以:
- 在程序暂停时,把编辑光标放到目标代码行。
- 按
Alt + F9(Run to Cursor)。
例如停在断点 A 时,把光标放到 System.out.println(ret);,执行后会在这行暂停,便于查看 ret。
目标位置之前的代码仍会执行;如果途中先命中其他断点,会先在那个断点暂停。目标行也必须是本次执行能够到达的位置。
3. 重新调试与停止
| 操作 | 快捷键 | 含义 |
|---|---|---|
| Rerun(重新调试) | Ctrl + F5 |
从头重新启动当前调试配置,适合重新观察执行过程 |
| Stop(停止) | Ctrl + F2 |
终止当前程序,结束这次运行 |
Ctrl + F2 是停止,不是可继续执行的暂停。 停止之后,要再次运行就需要重新启动。参见 IDEA 调试会话说明。
六、管理断点与设置条件断点
1. 查看、禁用和屏蔽断点
-
View Breakpoints:
Ctrl + Shift + F8。集中查看、编辑、启用、禁用或删除断点。 - Mute Breakpoints(屏蔽断点):在 Debug 工具栏点击该按钮,临时屏蔽所有断点;再次点击可解除屏蔽。断点仍然保留,不需要重新添加。
禁用适合暂时保留某个断点,删除适合不再需要的断点,屏蔽适合临时让程序不因这些断点而停下。
2. 条件断点:只在关心的那一次暂停
循环执行很多次时,普通断点会反复暂停。如果只想检查 i == 20 的情况,可以设置条件断点。
把 main 方法中的代码替换为:
for (int i = 0; i < 100; i++) {
System.out.println(i); // 在这一行设置断点
}
操作步骤:
- 在
System.out.println(i);行设置断点。 - 右键单击断点红点,在
Condition中填写i == 20。 - 启动 Debug。
程序会正常执行前面的循环,直到 i == 20 时,才在该断点暂停。此时:
- Debugger 中的
i为20。 - Console 已输出
0到19。 -
20还没有输出,因为当前打印语句尚未执行。
条件应是结果为 true 或 false 的表达式。这里写 i == 20,不是赋值表达式 i = 20,也不需要写 if 或分号。
条件断点只控制是否暂停,不会让条件不满足时的代码停止执行。
七、把调试操作用来定位错误
假设加法示例中的返回语句误写成:
return x - y;
预期结果是 30,实际却输出 -10。可以这样定位:
- 在
int ret = add(a, b);处打断点并 Debug。 - 查看
a = 10、b = 20,确认调用前的数据正确。 - 按
F7进入add,确认x = 10、y = 20,参数传递也正确。 - 执行到返回语句,对比预期的
x + y与实际代码x - y,发现运算符写错。 - 改为
return x + y;,重新调试,确认ret = 30,最终输出正确。
调试时每一步都应带着一个检查目标:输入是否正确、哪一步计算有误、返回结果是否符合预期。 找到第一个出现偏差的位置,通常就能明显缩小错误范围。
Top comments (0)