---
url: /article/k0xruml6/index.md
---
## 前言

正所谓工欲善其事必先利其器，IDEA 是一款很强大的 Java IDE，无论是在项目开发阶段，还是阅读 JDK 或其他框架源码，断点调试都是很有必要的。但大多数情况下我们只使用了简单的调试方方式，这一篇将介绍详细介绍 IDEA 的断点调试方式。

## 1. 基础篇

首先贴上准备好的示例代码。

```java
import com.mayee.bean.Person;
import com.mayee.service.IService;
import com.mayee.service.impl.IServiceImpl;

import java.math.BigInteger;

public class DebugTest {
    public static void main(String[] args) {
        line();
        detailLine();
        method();
        exception();
        field();
        thread();
    }

    /**
     * 行断点
     */
    private static void line() {
        System.out.println("this is the line break point.");
    }

    /**
     * 详细断点(源断点)
     */
    private static void detailLine() {
        System.out.println("this is the detail line break point.");
    }

    /**
     * 方法断点 | 接口跳转实现类
     */
    private static void method() {
        System.out.println("this is from method.");
        IService iService = new IServiceImpl();
        iService.execute();
    }

    /**
     * 异常断点 | 全局捕获
     */
    private static void exception() {
        Object o = null;
        o.toString();
        System.out.println("this line will never be print.");
    }

    /**
     * 属性断点 | 读写监控
     */
    private static void field() {
        Person person = new Person("maye", 26);
        person.setAge(27);
        System.out.println(person);
    }

    /**
     * 多线程断点
     */
    private static void thread() {
        // 第一个线程计算 100 累加
        AddThread t1 = new AddThread(100);
        // 第二个线程计算 100000 累加
        AddThread t2 = new AddThread(100000);

        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        t2.start();

        try {
            // 线程 join 进主线程(main)，以使在 t1 和 t2 执行结束前，主线程不会执行下一步
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        BigInteger result = t1.getResult().add(t2.getResult());
        System.out.println("two thread result add:" + result);
    }

    private static class AddThread extends Thread {
        private BigInteger result = BigInteger.ZERO;
        private final long num;

        public AddThread(long num) {
            this.num = num;
        }

        @Override
        public void run() {
            System.out.printf("%s start calculate: %d%n", Thread.currentThread().getName(), num);
            add(num);
            System.out.println(Thread.currentThread().getName() + "execute complete.");
        }

        // 累加计算
        public void add(long num) {
            for (int i = 1; i <= num; i++) {
                result = result.add(BigInteger.valueOf(i));
            }
        }

        public BigInteger getResult() {
            return result;
        }
    }
}
```

```java
package com.mayee.bean;

public class Person {

    private String name;
    private int age;

    public Person() {

    }

    public Person(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(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
```

```java
package com.mayee.service;

public interface IService {
    void execute();
}
```

```java
package com.mayee.service.impl;

import com.mayee.service.IService;

public class IServiceImpl implements IService {
    @Override
    public void execute() {
        System.out.println("method executed.");
    }
}
```

接下来开始表演。

### 1.1. 行断点

在 `main()` 方法中注释掉其他方法，只留下 `line()` 方法。
在如图所示的位置点击鼠标左键便设置了一个断点，然后以 debug 方式启动程序，程序会执行到断点这一行挂起。这是最基本也是最常用的断点调试方式，因此就不再赘述。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.1_01.webp)

### 1.2. 详细断点

在 `main()` 方法中注释掉其他方法，只留下 `detailLine()` 方法。
在如图所示的位置点击按下 **shift** + **鼠标左键** 便可以打开断点设置面板，如图所示勾选，然后点击右下角的 **DONE**。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.2_01.webp)

我们可以看到断点的颜色是黄色，而不是之前的红色，此时以 debug 方式启动程序，会发现程序直接执行完成，而不会在断点处挂起。
但是我们可以看到控制台输出了断点到达行的详细信息。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.2_02.webp)

如果我们就是想要程序停在断点处呢？
右键单击黄色断点，打开设置面板，勾选 **Suspend**，意为 **挂起**，然后点击右下角的  **Done**。再次以 debug 方式启动程序，便会在停在断点处了。
此时我们发现断点从黄色变成了红色，聪明的你可能会问了，这不是脱了裤子放 P 吗，没错就是给你看看这 PP 有多白 :smirk:。言归正传，这个设置面板还有其他用处，稍后再讲。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.2_03.webp)

### 1.3. 方法断点

在 `main()` 方法中注释掉其他方法，只留下 `method()` 方法。
在 `mtehod()` 方法处点击鼠标左键便设置了一个方法断点。我们可以看到断点是红色菱形的，此时以 debug 方式启动程序，会发现程序会停在方法的第一行。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.3_01.webp)

此时我们按下 **F9**，程序会停在该方法的最后一行。
这就是方法断点，只需要在方法处下一个断点，IDEA 就会自动在方法的首尾两处挂起，方便查看入参和返回的情况。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.3_02.webp)

在本方法中，我们调用了 `IService` 接口的方法 `execute()`。假如 `iService` 对象是外部调用的入参呢，可能 `IService` 接口有多个实现类，我们想观察 `execute()` 方法的内部执行情况，但不知道这里究竟调用的是哪个实例中的 `execute()` 方法，该如何断点？
此时我们只需要在 `IService` 接口中的 `execute()` 方法处进行断点即可，因为在方法出下断点，因此该断点也是红色菱形的。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.3_03.webp)

此时以 debug 方式启动程序，会发现程序停在 `IServiceImpl` 类中 `execute()` 方法的第一行。这里有个菱形的断点标识，是程序自动显示的，并不是手动添加的。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.3_04.webp)

若按下 **F9**，程序会像如上那样停在方法的结尾处，这里就不再图示了。

### 1.4. 异常断点

在 `main()` 方法中注释掉其他方法，只留下 `exception()` 方法。
我们来看 `exception()` 方法体中，这里我们故意制造了一个会产生 `NullPointerException` 异常的调用，这里不需要打任何断点，只需要点击如图所示或按下 **Ctrl**+**Shift**+**F8**，打开更详细的断点设置面板。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.4_01.webp)

如图所示，这里有一个 **Java Exception Breakpoints** 的选项没有被勾选。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.4_02.webp)

点击左上角的 **＋**，并选择 **Java Exception Breakpoints**。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.4_03.webp)

会弹出一个面板，我们搜索并选中 **NullPointerException**，然后点击右下角的 **OK**。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.4_04.webp)

此时就添加并自动勾选了 **NullPointerException** 的选项，然后点击右下角的 **Done**。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.4_05.webp)

表示会对此种异常进行捕捉。当程序中任意位置产生了此种异常时，便会在异常的代码行处挂起。
此时以 debug 方式启动程序，会发现程序停止在了异常的代码行。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.4_06.webp)

### 1.5. 属性断点

在 `main()` 方法中注释掉其他方法，只留下 `field()` 方法。
例如，我们想要监视 `Person` 类中的 `age` 属性，那么我们只需要在 `age` 属性前打一个断点。我们发现，这个断点是一个红色的眼睛，这很好理解，实心的红色表示断点启用，眼睛表示监视。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.5_01.webp)

此时以 debug 方式启动程序，会发现程序停在了 `Person` 类的构造方法处。
因为在 `field()` 方法中，第一次使 `age` 属性值改变的代码是 `Person person = new Person("maye", 26);`，因此程序会挂起在 `Person` 类的构造方法中改变 `age` 属性值的这一行。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.5_02.webp)

接着，我们按下 **F9** 会发现程序在停在 `setAge()` 方法的这一行。因为，在 `field()` 方法中，第二次使 `age` 属性值改变的代码是 `person.setAge(27);`，因此程序会挂起在 `Person` 类的 `setAge()` 方法中改变 `age` 属性值的这一行。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.5_03.webp)

### 1.6. 多线程断点

前文我们说到在断点设置面板中，有一个 **Suspend** 的选项，这里我们再来看一下。
在如图所示处下断点，并右键单击断点打开设置选项卡，发现 **Suspend** 是自动被勾选的，后面还有 **All** 和 **Thread** 两个选项。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.6_01.webp)

**All** 与 **Thread** 之间的区别：

* All: 表示任何线程执行这此处都会挂起，此时程序也不会断点到子线程的方法中。
* Thread: 表示以线程为单位挂起，此时程序会断点到子线程的的方法中。

此时，以 debug 方式启动程序，看到如图所示这一块，这里学名叫做 **Call Stack**，即为调用栈。
此时可以看到除了 **main** 线程外，还有 **t1**、**t2** 两个线程。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.6_02.webp)

按下 **F9** 让程序执行完。
我们再次打开断点设置选项卡，并切换勾选到 **Thread**，此时发现后面多出了一个 **Make Default** 的按钮，这个按钮表示是将 **All** 或 **Thread** 设置为默认的断点方式，后续直接下的断点就会以这种方式挂起。因此各位请按照实际情况执行决定默认以哪种方式，这里我就不修改断点的默认方式了，直接点击右下角的 **Done**。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.6_03.webp)

然后，以 debug 方式启动程序，再次查看调用栈，发现只能看到 **main** 线程。因为断点处的代码是在 `main()` 方法中，线程上下文是在 **main** 线程中，而我们是以线程为单位挂起的，所以这里就只能看到 **main** 线程的调用栈。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.6_04.webp)

按下 **F9** 让程序执行完。
接着我们在如图所示处打一个断点，再次以 debug 方式启动程序。此时查看调用栈，发现可以看到 **t1**、**t2** 的线程，但 **t1** 线程前面有一个 **√**，这表示当前的线程的上下文是在 **t1** 这个线程中，我们同时可以看到右侧 num 变量的值为 100。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.6_05.webp)

如果我们在调用栈中选中 **t2** 这个线程呢，此时便可以看到 **t2** 线程的 num 值为 100000。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.6_06.webp)

当我们选中 **t2** 线程时，这是否意味着我们就将线程调度到了 **t2** 线程呢？此时，我们选中当前断点行处的 `Thread.currentThread().getName()` 代码，然后点击 **Evaluate Expression** 按钮，或按下 **Alt** + **F8**，呼出 **Evaluate** 面板。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.6_07.webp)

然后点击 **Evaluate** 按钮，可以看到当前的线程名是 **t1**，而我们发现 **√** 依然是在 **t1** 线程前面，说明当前的活动线程依然是 **t1** 线程，此时在调用栈中切换并没有起到切换线程上下文的效果。因为此处断点的 **Suspend** 选项 **All**，即表示所有线程，哪个线程先执行到此处，就将线程上下文给到哪个线程。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.6_08.webp)

按下 **F9** 让程序执行完。
右键单击此处断点，将 **Suspend** 切换到 **Thread** 选项，然后点击 **Done**。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.6_09.webp)

再次以 debug 方式启动程序，然后在调用栈中选中 **t2** 线程，接着按下 **Alt** + **F8**，再次输入 `Thread.currentThread().getName()`，然后点击 **Evaluate**，可以看到当前的线程名已经是 **t2**，并且 **√** 也在 **t2** 线程前面。说明此时的活动线程为 **t2**，而线程上下文也被切换到了 **t2** 线程。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_1.6_10.webp)

多线程调试的意义在于，各线程副本之间互不干扰，可以随时在多个线程之间切换上下文，观察线程内部的执行情况。

## 2. 进阶篇

首先贴上准备好的示例代码。

```java
import com.mayee.bean.Person;
import com.mayee.service.IService;
import com.mayee.service.impl.IServiceImpl;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class DebugAdvance {

    public static void main(String[] args) {
        conditions();
        printStackTrace();
        evaluate();
        saveRecourse();
        keysExplain();
        sourceCode();
        streamDebug();
    }

    /**
     * 条件表达式
     */
    public static void conditions() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }

        Runnable myThread = () -> {
            System.out.println(Thread.currentThread().getName() + " -- come in");
            try {
                TimeUnit.MILLISECONDS.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + " -- leave out");
            }
        };

        Thread t1 = new Thread(myThread, "t1");
        Thread t2 = new Thread(myThread, "t2");
        Thread t3 = new Thread(myThread, "t3");

        t1.start();
        t2.start();
        t3.start();
    }

    /**
     * 打印堆栈信息
     */
    public static void printStackTrace() {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        System.out.println(list);
    }

    /**
     * 表达式解析
     */
    public static void evaluate() {
        System.out.println("evaluate");
        Person person = new Person("maye", 26);

        List<Integer> list = Stream.of(1, 2, 3, 4).map(i -> i * 2).collect(Collectors.toList());
    }

    /**
     * 避免操作资源 | force return
     */
    public static void saveRecourse() {
        System.out.println("shit happens");

        System.out.println("save to db");
        System.out.println("save to redis");
        System.out.println("send message to mq");
    }

    /**
     * 快捷键、图标含义
     */
    public static void keysExplain() {
        System.out.println("keys");
        // step over
        System.out.println("step over");

        // step into | step out
        System.out.println("step into | step out");
        IService iService = new IServiceImpl();
        iService.execute();

        // force step into
        StringBuffer buffer = new StringBuffer();
        buffer.append("hello world");
        System.out.println(buffer.toString());

        // run to cursor
        System.out.println("i am here");
        System.out.println("i am here");
        System.out.println("i am here");
        System.out.println("i am here");
    }

    /**
     * 源码调试 | JDK
     */
    public static void sourceCode() {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        System.out.println(list.size());

        LinkedList<Integer> linkedList = new LinkedList<>();
        linkedList.add(1);
        linkedList.add(2);
        linkedList.add(3);
        System.out.println(linkedList.size());
    }

    /**
     * stream 调试
     */
    public static void streamDebug() {
        // steam chain
        Stream.of(1, 2, 3, 4, 5, 6)
                .filter(i -> i % 2 == 0 || i % 3 == 0)
                .map(i -> i * i)
                .forEach(System.out::println);

        // evaluate 曲线救国
        String str = Optional.of("hi,")
                .map(s -> s + "Java")
                .map(s -> s + "技术")
                .map(s -> s + "栈")
                .get();
        System.out.println(str);
    }
}
```

### 2.1. 条件表达式

在 `main()` 方法中注释掉其他方法，只留下 `conditions()` 方法。
在如图所示的设置一个断点，并在 **Condition** 处设置一个条件表达式 `i % 2 == 0`，然后点击右下角的 **Done**。此时可以看到断点是红色圆形，并且右下侧有一个 **?** 标识。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.1_01.webp)

IDEA 会自动识别到当前程序的上下文变量，这个条件表示当变量 i 的值为偶数时，断点才生效。
然后以 debug 方式启动程序，可以看到只有 i 的值为 2, 4, 6, 8 的时候才会进入断点。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.1_02.webp)

那是否说明，只有 i 为偶数时程序才被执行到了呢？并不是，我们可以看控制台，发现 i 从 0 ~ 9 都被打印出来了，仅仅是 i 为偶数才会挂起程序。

同理，我们也可以在线程中设置断点的条件表达式。这里我们设置只有当线程名为 **t1** 时才将程序挂起。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.1_03.webp)

此时以 debug 方式启动程序，可以看到当前的挂起的线程是 **t1**，也只有 **t1** 会被挂起，**t2** 和 **t3** 线程均不会被挂起。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.1_04.webp)

### 2.2. 打印断点堆栈信息

在 `main()` 方法中注释掉其他方法，只留下 `printStackTrace()` 方法。
在如图所示的设置一个断点，并点击 **View Breakpoints** 或按下 **Ctrl** + **Shift** + **F8**，打开断点详情面板。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.2_01.webp)

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.2_02.webp)

然后勾选 **"Breakpoint hit" message** 和 **Stack trace**，点击右下角 **Done**。
以 debug 方式启动程序，可以看到控制台打印出了断点处的调用栈信息。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.2_03.webp)

### 2.3. 表达式解析

在 `main()` 方法中注释掉其他方法，只留下 `evaluate()` 方法。
在如图所示处打断点，以 debug 方式启动程序，然后按下 **Alt** + **F8**，可以进行变量计算。注意，只有作用域在断点处上下文中的变量才能被计算。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.3_01.webp)

### 2.4. 避免操作资源

在 `main()` 方法中注释掉其他方法，只留下 `saveRecourse()` 方法。
在如图所示处打断点，然后以 debug 方式启动程序。
这里我们模拟方法中的资源操作，在业务逻辑 `shit happens` 的后面有三个操作，分别是将数据存入数据库、Redis，发送消息到 MQ 中。假如我们执行到了 `shit happens` 处，却发现数据有问题，不想执行后面的三个操作，以免写入脏数据，该如何做呢？
你可能会说，这还不简单，直接停止程序就好了。但是这样真的可以吗？我们在断点处停止了程序，然后看到后面的三行代码依然被打印在了控制台，这意味着这些代码依然被执行了，即使我们停止了程序。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.4_01.webp)

如何才能使后面的代码不被执行呢？
此时，再次以 debug 方式启动程序。然后在调用栈中的 `saveRecourse()` 方法栈帧处单击右键，选中 **Force Return**，即表示当前栈帧(`saveRecourse()` 方法)强制返回，程序会走到上层调用完它的位置，此处即为 `main()` 方法的结尾处。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.4_02.webp)

### 2.5. debug 按钮说明

在 `main()` 方法中注释掉其他方法，只留下 `keysExplain()` 方法。
我们可以看到 IDEA 的调试面板，从左到右有 8 个按钮，依次编号为：1 ~ 8。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.5_01.webp)

1. Show Execution Point(Alt+F10)：当我们在看方法调用链的时候，会一层一层的进入到方法内部看，如果想要快速回到断点的位置查看，就可以点击这个按钮。
2. Setp Over(F8)：意为**步过**，按下表示程序从断点处向下执行一行，如果下一行代码是个方法，它不会进入到方法内部。
3. Step Into(F7)：意为**步入**，如果断点执行到当前行是一个方法，则可进入到方法内部。
4. Force Step Into(Alt+Shift+F7)：意为**强制步入**，若当前断点执行处的方法源码在我们本地，则使用**步入**即可进入方法内部，若当前断点执行处的方法源码不在本地，例如是 Jar 包引入的三方库，则需要用**强制步入**才可以进入到方法内部。
5. Step Out(Shift+F8)：意为**步出**，若我们在查看一个方法内部中的执行情况，看到一半不想看了，可以使用**步出**，程序将走到上层调用此方法的位置(此处表示 `main()` 方法中调用 `keysExplain()` 方法返回的位置)，`keysExplain()` 方法不会被再次调用。**步入**和**强制步入**都可以使用**步出**。
6. Drop Frame：表示丢弃当前(栈)帧，程序会走到上层调用此方法处(此处表示 `main()` 方法中调用 `keysExplain()` 方法的位置)。但是要注意，当程序走到 `keysExplain()` 方法中时，选择丢弃当前帧，即表示将 `keysExplain()` 方法从栈帧中丢掉，然后重新调用一次 `keysExplain()` 方法。
7. Run to Cursor(Alt+F9)：按下吃按钮表示，程序会直接跳转到鼠标光标所在行的代码处。注意，程序只能从上向下执行，若将光标定位到断点前的代码，那么程序直接就结束了。
8. Evaluate Expression(Alt+F8)：表示可以对变量进行表达式计算。

### 2.6. 源码调试

在 `main()` 方法中注释掉其他方法，只留下 `sourceCode()` 方法。
在如图所示处打断点，然后以 debug 方式启动程序。
当我们想进入方法内部的某一行代码查看时，可以点击 **Resume Program** 或按下 **F9**，表示程序跳转到下一个断点处。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.6_01.webp)

### 2.7. Stream 调试

当我们使用 Java8 使我们在变成方式上有很大变化，其中一个就是流式处理，它允许我们将数据以数据流的方式，进行管道处理。不熟悉流式编程的开发者会很不习惯，认为代码难以阅读，搞不清楚里面做了什么操作。但是当用熟了之后，便会直呼：“妈呀，真香！”。可用上了流式编程后，该如何调试呢？
在 `main()` 方法中注释掉其他方法，只留下 `sourceCode()` 方法。
在如图所示处打断点，但这里有 3 个选项 **Line**、**λ**、**All**。

* Line：表示将 Stream 流的链式表达当做一行，即不会在流的中间断点显示变量值。
* λ：表达将 Stream 流的链式表达的每一个节点当做一行，会在流的中间断点显示变量值。
* All：表示两者兼具，也会在流的中间断点显示变量值。

这里我们将选择以 **Line** 的方式断点，然后以 debug 方式启动程序。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.7_01.webp)

点击下方的 **Trace Current Stream Chain**。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.7_02.webp)

会打开一个 Stream Trace ，即流的追踪面板。
我们可以看到在此面板中，将程序中流的每一步数据操作的过程及结果都做了对应，可以很直观的看出数据在经过流中每一步操作之后的变化。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.7_03.webp)

默认是 **Split Mode** 视图分割模式，可以在上面的几个选项卡之间切换视图，当然也可以点击左下角的 **Flat Mode** 切换为扁平模式，数据将会在一个视图中展示，之后也可以点击走下角的 **Split Mode** 再切回视图分割模式。

![](https://cdn.jsdelivr.net/gh/Ma-yeah/picx-images-hosting@master/2021-12-25/01_2.7_04.webp)

***

至此，IDEA 的 debug 方式基本介绍完，熟练掌握之后有助于在开发中提高工作效率，也有助于提高阅读源码的效率。
