Debug的引入和概述
IDEA中Debug的使用
Debug演示
一、前言
在我们以往的程序执行中,只能看到控制台上展示的最终结果,无法直观清晰地看到程序内部每一个变量的加载,更迭,以及代码执行的内部逻辑。而Debug(断点调试),可以让我们打破这层壁障。就像三体中的从四维碎片看三维中的人类一样,人类的每一根血管每一个内部器官都清晰展现在眼前。Debug可以把程序的每一个细节和每一次变化展现在我们面前,让我们直观地看到程序执行的过程,了解代码执行的来龙去脉。(PS : 以IDEA演示)
二、概述 :
断点调试是指在程序的某一行设置一个断点,调试时,程序执行到断点处就会停止,之后可以由程序员决定是否要继续往下调试,你可以手动控制一步一步往下执行,直至程序执行完毕;也可以设置多个断点,并直接跳转到下一个断点;或者直接退出调试。若程序报错,可以调试到出错代码行并停下,通过分析找到当前Bug。熟练掌握Debug,不仅可以帮助我们更容易理解源码,也可以帮助我们在开发中更容易找到Bug。
注意 :
在断点调试过程中,程序处于运行状态,且以堆内存中真正的对象来执行。
调试过程中可以看到各个变量当前的值。
调试过程中可以动态下断点。
若代码中一个断点都没有设置,那么Debug和普通运行的效果是一样的。
三、如何使用 :
1.IDEA中Debug常用的五个快捷键介绍 :
① F7 (跳入):跳入方法内。当调试到某个调用方法的语句时,你可以选择按下F7跳入该方法。
② Alt + Shift + F7 (跳入_牛逼版):强制跳入方法内。
PS :说到“强制”的问题就不得不提一嘴—— IDEA默认情况下不让你随便进入源码,但我们可以通过配置进行修改:
依次点击Setting ——> Build,Execution, Deployment ——> Debugger ——> Stepping,然后将“Do not step into the classes”中的java.*和javax.*取消勾选即可,如下GIF演示图:
③ F8(跳过):逐行执行代码,按一下F8就执行一行。(如果有循环结构,直到跳出循环)
④ Shift + F8 (跳出):跳出方法,与F7效果相对。比如,当我想在调试过程中查看方法源码时,我可以使用F7或者Alt + Shift + F7跳入方法;当我不想看了,想回去原来跳入方法前的地方,那么按下Shift + F8就 🆗了。
⑤ F9( resume(中断后继续)):按下F9可以立即执行到下一个断点处。
2.Debug界面图示 :
up先随便找个等会儿准备演示Debug的类,点击Debug即可进入Debug界面,如下图所示 :
Debug界面图如下所示 :
下面up带大家简单“解剖”一下这张Debug界面图 :
首先,最上面这一栏是重要的功能区,如下图所示 :
①左面的Debugger 和 Console分别代表了Debug界面 和 控制台界面,当Console左下角出现小图标时(就比如现在这样),说明控制台有信息打印出来,你可以点击Console跳转过去查看。
②对于右面挨过来的这几个小图标(按照我们之前介绍五个快捷键时的顺序来) :
| F7 (跳入) |
| Alt + Shift + F7 (跳入_牛逼版) |
| F8(跳过) |
| Shift + F8 (跳出) |
③其次,下面那一块白色框区域,是实时显示变量的更迭和变化情况的,也就是变量区。
④最左面那一栏,小按钮一堆,不用说也肯定是个功能区。这里我们只说两个按钮。
| F9(resume(中断后继续)) |
| 结束按钮(结束此次Debug过程) |
3.IDEA相关设置更改 :
IDEA中,默认对Debug的界面显示做了简化,导致有些东西我们在Debug中是看不到,比如集合中的一些空(null)元素。我们可以通过更改IDEA的设置来解决这个问题。依次点击Settings ——> Build, Execution, Deployment ——> Debugger ——> Data Views ——> Java,如下图所示 :
然后取消选中"Hide null elements in arrays and collections",和"Enable alternative view for Collections classes"这两个选项,就🆗了。如下图所示 :
四、代码演示 :
1.演示Ⅰ——测试for循环中变量的更迭情况 :
up以Demo_one类作为第一个演示类,Demo_one类代码如下 :
package knowledge.debug.demo1;
//测试Debug中的变量
public class Demo_one {
public static void main(String[] args) {
//测试for循环中变量的更迭
int sum = 0; //断点设置在第7行
for (int i = 0; i < 5; ++i) {
sum += i;
System.out.println("i = " + i);
System.out.println("sum = " + sum);
System.out.println("-------------------------------");
}
System.out.println("for循环已经结束,程序退出。");
}
}
在第七行设置一个断点,进入Debug后通过F8(跳过)进行逐行调试。大家一定要仔细观察变量区中变量的变化情况。这里还要再补充一句,Debug过程中,代码行标出高亮的部分表示当前待执行的代码行,并不是已经执行的代码行,只有当跳过该行代码时,该行代码才算执行完毕,并且该行代码中涉及的变量的变化和加载也会在变量区展现出来。
GIF演示图如下 : (由于文件较大,因此up按步骤分为多个GIF图。
①首先,进入Debug界面,并观察变量区中sum变量和i变量的加载:
②查看终端中i和sum变量的输出 :
③查看for循环中i变量和sum变量的变化情况 :
④继续执行for循环,直到i不满足for循环的条件语句,程序退出 :
2.演示Ⅱ——测试数组下标越界异常:
up以Demo_two类作为第二个演示类,Demo_two类代码如下 :
package knowledge.debug.demo2;
//测试数组下标越界异常
public class Demo_two {
public static void main(String[] args) {
int[] array = new int[]{11, 5, 211, 985, 77};
for (int i = 0; i <= array.length; i++) { //
System.out.println("数组的第" + (i+1) + "个元素为 : " + array[i]);
}
System.out.println("-------------------------------");
System.out.println("for循环结束,程序退出。");
}
}
在第6行设置断点,进入Debug后通过F8(跳入)逐行调试代码。注意,数组下标都是从0开始的,所以我们一般用for循环遍历数组时,循环变量的范围通常是0 ≤ i < array.length。假设将i的范围写作现在这个样子:0 ≤ i ≤ array.length,因为数组最大的下标是array.length - 1,根本不存在array.length这个下标,因此肯定会报数组下标越界异常。
GIF演示图如下 :
大家注意看,当执行完第6行定义数组的代码时,变量区可以清楚地将数组的信息加载出来,比如数组的长度,以及每个元素的索引和值分别是什么。最重要的是,当i变量的值变成5时,理论上数组已经没有下标为5的元素了,但是我们就是要测试越界异常嘛,i = 5是仍然满足for循环的条件语句的。但是,当i = 5时,
变量区已经提示我们array[i] = java.lang.IndexOutOfBoundsException..... 如下图所示 :
这时候,如果接着执行F8操作,jvm就会在控制台打印出异常信息,并告诉你下标5越界啦。如下图所示 :
3.演示Ⅲ——测试F7(跳入方法)和F8(跳出方法):
up以Demo_three类作为第三个演示类,Demo_three类代码如下 :
package knowledge.debug.demo3;
import java.util.Arrays; //记得导包
//测试跳入方法源码和跳出方法源码
public class Demo_three {
public static void main(String[] args) {
int[] array = new int[]{11, 5, 985, 211, 11};
Arrays.sort(array); //该方法可以将传入的数组升序排列
System.out.println("升序排列后的数组如下 : ");
System.out.println("-------------------------------");
for (int i = 0; i < array.length; i++) {
System.out.println("数组的第" + (i + 1) + "个元素为 : " + array[i]);
}
System.out.println("-------------------------------");
System.out.println("for循环结束,程序退出。");
}
}
在第8行设置断点,进入Debug后通过F8(跳过)进行逐行调试。
开始之前,先介绍一下Arrays类的sort() 方法,sort() 方法是Arrays类的静态方法,可以对传入的数组进行升序排序。在测试Ⅲ中,当我们调试到使用sort() 方法的代码行(即第9行)时,按下F7可以跳入sort方法,即进入sort方法的源码。当然,你可以一直通过F7跳入方法的最底层。
然后通过Shift + F8就可以跳出方法,一直跳回Demo_three类。接着F8继续逐行调试。
GIF演示图如下 :
4.演示Ⅳ——测试F9(调试过程中直接跳到下一个断点处):
up以Demo_four为第四个演示类,Demo_four类代码如下 :
package knowledge.debug.demo4;
//测试Debug过程中动态的下断点。(F9——resume可以直接跳到下一个断点)
public class Demo_four {
public static void main(String[] args) {
int[] array = new int[]{5, 11, 985, 211, 5};
for (int i = 0; i < array.length; i++) {
System.out.println("数组中第" + (i + 1) + "个元素为 :" + array[i]);
}
System.out.println("-----------------------------------------------");
System.out.println("梯田");
System.out.println("爱在西元前");
System.out.println("同一种调调");
System.out.println("龙卷风");
System.out.println("伊斯坦堡");
System.out.println("夜的第七章");
System.out.println("米兰的小铁匠");
}
}
up先在第6行——即定义数组的代码行给一个断点,进入Debug后使用F8操作进行逐行调试,
在调试过程中,假设——当for循环语句输出985时,我们在第13行(爱在西元前)和第16行(伊斯坦堡)分别加上断点;接着我们按下F9就可以快速跳转到下一个断点处,并且仍然由你决定接下来的操作——是直接结束Debug,还是继续逐行调试,或者再跳转到下一个断点处。注意,使用F9操作后,两个断点之间的代码会直接执行完毕,而不是挨个执行了。
GIF演示图如下 :
5.演示Ⅴ——测试创建对象过程
演示五中,我们要测试一下创建对象的流程,主要就是想看看对象初始化的三个阶段(默认初始化 ---> 显式初始化 ---> 构造器初始化)。本次演示中,我们以创建KunKun类对象为栗,KunKun类代码如下 :
package knowledge.debug.demo5;
public class KunKun {
private String name;
private int age = 18;
public KunKun() {
}
public KunKun(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge() {
this.age = age;
}
@Override
public String toString() {
return "KunKun{" +
"name ='" + name + '\'' +
", age =" + age +
'}';
}
}
可以看到,KunKun类up还是以JavaBean标准来写了,给出了KunKun类的无参有参构造,成员变量的setter方法,并且up还重写了toString() 方法,以便于打印出KunKun类对象的基本信息。需要注意的是,KunKun类中的两个成员变量name和age,其中name变量未进行显式初始化,因此直接输出name为默认值null,而age变量进行了显式初始化,直接输出age为显式初始化之后的结果,即18。当然,若通过有参构造创建KunKun类对象,那么输出结果便是构造器初始化后的结果。
up以Demo_five为第五个演示类,Demo_five类代码如下 :
package knowledge.debug.demo5;
//通过Debug分析创建对象的流程
public class Demo_five {
public static void main(String[] args) {
//创建KunKun类对象 :
//1°加载KunKun类的字节码文件
//2°初始化(默认初始化 ——> 显式初始化 ——> 构造器初始化)
//3°返回对象的地址值给引用
KunKun kunKun = new KunKun();
System.out.println(kunKun);
kunKun.setName("你干嘛~");
System.out.println(kunKun);
KunKun kunBro = new KunKun("坤哥", 24);
System.out.println(kunBro);
}
}
在第10行设置断点,进入Debug后通过F8(跳过)进行逐行调试。
注意看,在测试类中,我们是先利用无参构造创建了一个KunKun类对象kunKun,那么这时候她的两个属性值name和age就应该分别是null和18;接着,我们通过name变量的setter方法修改了kunKun对象的name属性值,那么再次输出name变量的值就应该是修改后的值。之后,我们又通过有参构造创建了一个KunKun类对象kunBro,那么这时kunBro对象的两个属性name和age 的值就应该是构造器初始化后的值了。
GIF演示图如下 :
6.演示Ⅵ——测试动态java动态绑定机制 :
第六个演示就不需要新建测试类哩,还记得up之前讲过的java动态绑定机制吗?当然,有些小伙伴儿可能没看过。在这篇博文里,up最后的课堂练习举了“找朋友”的例子。我直接把代码给大家放下面吧 :
package knowledge.polymorphism.auto_bind;
public class Find_friend { /** 测试类:找朋友类 */
public static void main(String[] args) {
People people = new Friends();
System.out.println("普通版找朋友,找到了谁————" + people.make_friends());
System.out.println("牛逼版找朋友,找到了谁————" + people.make_friends_EX());
}
}
class People { /** 父类:People类 */
String name = "刻晴";
public String getName() {
return name;
}
public String make_friends() {
return getName() + " and " + "琪亚娜";
}
public String make_friends_EX() {
return make_friends() + " and " + "周杰伦";
}
}
class Friends extends People { /** 子类:Friends类 */
String name = "吴京";
public String getName() {
return name;
}
public String make_friends() {
return super.getName();
}
public String make_friends_EX() {
return super.make_friends_EX() + " and " + name;
}
}
好的,我们就在测试类第5行给下一个断点,进入Debug界面后,通过F7跳入调用的这两个方法,并一直跳到底,查看它们的执行流程;看看它到底是怎么找朋友的,也希望通过对这个案例的Debug,加深大家对于java 动态绑定机制的理解。
GIF演示图如下 :
五、总结 :
Debug不仅可以帮助我们看清程序执行的来龙去脉,帮助我们清楚地展现出变量的更迭和变化情况;而且在实际开发中,Debug可以帮助我们更轻松地看懂源码,更容易找到程序的bug,因此,熟练掌握Debug对于一个程序员来讲非常重要。当然,本篇博文主要限于Debug的快速入门。Debug的实际应用之后up也会出博文分享。感觉阅读!
System.out.println("-----------------------------------------------------------------------");