java 程序猿必备技能——Debug详解

  • Debug的引入和概述

  • IDEA中Debug的使用

  • Debug演示


一、前言

在我们以往的程序执行中,只能看到控制台上展示的最终结果,无法直观清晰地看到程序内部每一个变量的加载,更迭,以及代码执行的内部逻辑。而Debug(断点调试),可以让我们打破这层壁障。就像三体中的从四维碎片看三维中的人类一样,人类的每一根血管每一个内部器官都清晰展现在眼前。Debug可以把程序的每一个细节和每一次变化展现在我们面前,让我们直观地看到程序执行的过程,了解代码执行的来龙去脉。(PS : 以IDEA演示


二、概述 :

断点调试是指在程序的某一行设置一个断点,调试时,程序执行到断点处就会停止,之后可以由程序员决定是否要继续往下调试,你可以手动控制一步一步往下执行,直至程序执行完毕;也可以设置多个断点,并直接跳转到下一个断点;或者直接退出调试。若程序报错,可以调试到出错代码行并停下,通过分析找到当前Bug。熟练掌握Debug,不仅可以帮助我们更容易理解源码,也可以帮助我们在开发中更容易找到Bug。

注意 :

  1. 在断点调试过程中,程序处于运行状态,且以堆内存中真正的对象来执行。

  1. 调试过程中可以看到各个变量当前的值。

  1. 调试过程中可以动态下断点。

  1. 若代码中一个断点都没有设置,那么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就 🆗了。
F9resume(中断后继续)):按下F9可以立即执行到下一个断点处。

2.Debug界面图示 :

up先随便找个等会儿准备演示Debug的类,点击Debug即可进入Debug界面,如下图所示 :

Debug界面图如下所示 :

下面up带大家简单“解剖”一下这张Debug界面图 :

首先,最上面这一栏是重要的功能区,如下图所示 :

左面的Debugger Console分别代表了Debug界面 控制台界面,当Console左下角出现小图标时(就比如现在这样),说明控制台有信息打印出来,你可以点击Console跳转过去查看。

对于右面挨过来的这几个小图标(按照我们之前介绍五个快捷键时的顺序来) :

F7 (跳入)

Alt + Shift + F7 (跳入_牛逼版)

F8(跳过)

Shift + F8 (跳出)

其次,下面那一块白色框区域,是实时显示变量的更迭和变化情况的,也就是变量区。

最左面那一栏,小按钮一堆,不用说也肯定是个功能区。这里我们只说两个按钮。

F9resume(中断后继续)

结束按钮(结束此次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("-----------------------------------------------------------------------");

Java Debug是一种在代码执行过程中进行调试和排错的技术。通过在代码中设置断点,我们可以暂停程序的执行,并逐步检查变量的值和程序的流程,以便找到错误并进行修复。JavaIDE(集成开发环境)通常提供了调试工具,如在IDEA中,我们可以通过更改设置来显示集合中的空元素,以便更好地进行调试。 在Java Debug过程中,我们可以使用调试工具来监视变量的值、执行流程和方法的调用。我们可以使用断点来暂停程序的执行,然后逐步执行代码来检查变量的值和程序的行为。在IDEA中,我们可以通过单击行号旁边的空白区域来设置断点,或者使用快捷键Ctrl + F8来切换断点。 调试过程中,我们还可以使用调试工具的其他功能,比如查看变量的值、修改变量的值、观察表达式的值等。我们可以通过在IDEA的Debug视图中查看变量的值,或者使用System.out.println语句打印变量的值。例如,使用System.out.println("数组中第" + (i + 1) + "个元素为 :" + array[i]);可以在控制台输出数组中每个元素的值。 调试过程中,我们可以逐步执行程序,观察变量的值和程序的执行流程,以便找到错误并进行修复。当遇到问题时,我们可以使用调试工具来检查变量的值,查看方法的调用栈,以及查看程序的行为。通过调试,我们可以更好地理解代码的执行过程,快速定位并解决问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [java 程序猿必备技能——Debug详解](https://blog.csdn.net/TYRA9/article/details/128884528)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cyan_RA9

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值