如何使用lambda表达式调试stream().map(...)?

Fed*_*zza 104 java debugging lambda java-8

在我们的项目中,我们正在迁移到Java 8,我们正在测试它的新功能.

在我的项目中,我使用Guava谓词和函数来使用Collections2.transform和过滤和转换一些集合Collections2.filter.

在这次迁移中,我需要将例如guava代码更改为java 8更改.所以,我正在做的改变是这样的:

List<Integer> naturals = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10,11,12,13);

Function <Integer, Integer> duplicate = new Function<Integer, Integer>(){
    @Override
    public Integer apply(Integer n)
    {
        return n * 2;
    }
};

Collection result = Collections2.transform(naturals, duplicate);
Run Code Online (Sandbox Code Playgroud)

至...

List<Integer> result2 = naturals.stream()
    .map(n -> n * 2)
    .collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

使用guava我调试代码非常容易,因为我可以调试每个转换过程,但我关心的是如何调试例如.map(n -> n*2).

使用调试器我可以看到一些代码,如:

@Hidden
@DontInline
/** Interpretively invoke this form on the given arguments. */
Object interpretWithArguments(Object... argumentValues) throws Throwable {
    if (TRACE_INTERPRETER)
        return interpretWithArgumentsTracing(argumentValues);
    checkInvocationCounter();
    assert(arityCheck(argumentValues));
    Object[] values = Arrays.copyOf(argumentValues, names.length);
    for (int i = argumentValues.length; i < values.length; i++) {
        values[i] = interpretName(names[i], values);
    }
    return (result < 0) ? null : values[result];
}
Run Code Online (Sandbox Code Playgroud)

但它没有像Guava那样直接调试代码,实际上我找不到n * 2转换.

有没有办法看到这种转换或轻松调试此代码的方法?

编辑:我添加了不同评论的答案并发布了答案

感谢Holger回答我的问题的评论,使用lambda块的方法让我看到了转换过程并调试了lambda体内发生的事情:

.map(
    n -> {
        Integer nr = n * 2;
        return nr;
    }
)
Run Code Online (Sandbox Code Playgroud)

感谢Stuart Marks方法引用的方法也允许我调试转换过程:

static int timesTwo(int n) {
    Integer result = n * 2;
    return result;
}
...
List<Integer> result2 = naturals.stream()
    .map(Java8Test::timesTwo)
    .collect(Collectors.toList());
...
Run Code Online (Sandbox Code Playgroud)

感谢Marlon Bernardes回答我注意到我的Eclipse没有显示它应该是什么,peek()的使用有助于显示结果.

Mar*_*des 77

在使用Eclipse Kepler或Intellij IDEA(使用JDK8u5)时,我通常没有问题调试lambda表达式.只需设置断点并确保不检查整个lambda表达式(仅检查lambda体).

调试Lambdas

另一种方法是用来peek检查流的元素:

List<Integer> naturals = Arrays.asList(1,2,3,4,5,6,7,8,9,10,11,12,13);
naturals.stream()
    .map(n -> n * 2)
    .peek(System.out::println)
    .collect(Collectors.toList());
Run Code Online (Sandbox Code Playgroud)

更新:

我认为你会感到困惑,因为map它是intermediate operation- 换句话说:它是一个懒惰的操作,只有在执行后才会terminal operation执行.所以当你调用stream.map(n -> n * 2)lambda体时此刻并没有被执行.您需要设置断点并在调用终端操作后检查它(collect在本例中).

检查流操作以获得进一步说明.

更新2:

引用霍尔格的评论:

让这里变得棘手的是,对map和lambda表达式的调用是在一行中,因此行断点将停止在两个完全不相关的动作上.

在之后插入换行符map( 将允许您仅为lambda表达式设置断点.并且调试器不显示return语句的中间值并不罕见.将lambda更改为n -> { int result=n * 2; return result; } 允许您检查结果.再次,逐行踩踏时适当插入换行符......

  • @Fede:这里让人感到棘手的是对`map`和lambda表达式的调用是在一行中,所以一个换行符将停止在两个完全不相关的动作上.在map之后插入一个换行符(`允许你只为lambda表达式设置一个断点.调试器不显示`return`语句的中间值并不罕见.将lambda改为`n - > {int result = n*2; return result;}`允许你检查`result`.再次,逐行逐步插入换行符...... (6认同)

Dmy*_*huk 29

IntelliJ在这种情况下有一个很好的插件作为Java Stream Debugger插件.我建议你看一下这个:https: //plugins.jetbrains.com/plugin/9696-java-stream-debugger?platform = skipsuite

它通过添加Trace Current Stream Chain按钮扩展了IDEA调试器工具窗口,该按钮在调试器停止在Stream API调用链中时变为活动状态.

它具有用于处理单独流操作的良好界面,并使您有机会遵循应调试的某些值.

Java流调试器

  • 太糟糕了,这对于可选值无效,可选值可以增长到相同的复杂性 (2认同)

Stu*_*rks 22

调试lambda也适用于NetBeans.我正在使用NetBeans 8和JDK 8u5.

如果在存在lambda的行上设置断点,则实际上将在管道设置时点击一次,然后对每个流元素点击一次.使用您的示例,第一次点击断点将是map()设置流管道的调用:

第一个断点

您可以main按预期看到调用堆栈以及局部变量和参数值.如果继续步进,则再次点击"相同"断点,除非这次是在对lambda的调用中:

在此输入图像描述

请注意,这次调用堆栈位于流机制的深处,局部变量是lambda本身的本地,而不是封闭main方法.(我已经更改了naturals列表中的值以使其清楚.)

正如Marlon Bernardes指出的那样(+1),你可以用它peek来检查管道中的值.如果你从并行流中使用它,请小心.可以跨不同线程以不可预测的顺序打印值.如果您将值存储在调试数据结构中peek,那么该数据结构当然必须是线程安全的.

最后,如果你正在对lambdas进行大量调试(特别是多行语句lambdas),最好将lambda提取到一个命名方法中,然后使用方法引用引用它.例如,

static int timesTwo(int n) {
    return n * 2;
}

public static void main(String[] args) {
    List<Integer> naturals = Arrays.asList(3247,92837,123);
    List<Integer> result =
        naturals.stream()
            .map(DebugLambda::timesTwo)
            .collect(toList());
}
Run Code Online (Sandbox Code Playgroud)

这可能会让您在调试时更容易看到正在发生的事情.此外,以这种方式提取方法使单元测试更容易.如果你的lambda非常复杂,你需要单步执行它,那么你可能想要为它进行一系列的单元测试.

  • 我认为创建方法引用除了使方法更容易进行单元测试之外,还可以使代码更具可读性。很好的答案!(+1) (2认同)

Fed*_*zza 8

只是为了提供更多更新的细节(2019 年 10 月),IntelliJ 添加了一个非常好的集成来调试此类非常有用的代码。

当我们在包含 lambda 的行处停止时,如果我们按下F7(step into),那么 IntelliJ 将突出显示要调试的代码段。我们可以切换要调试的块Tab,一旦我们决定它,然后我们F7再次单击。

这里有一些截图来说明:

1- 按F7(步入)键,将显示高光(或选择模式) 在此处输入图片说明

2-使用Tab多次选择要调试的代码段 在此处输入图片说明

3- 按F7(step into) 键进入 在此处输入图片说明


Ego*_*gor 6

Intellij IDEA 15似乎让它更容易,它允许停在lambda所在的行的一部分,请参阅第一个功能:http://blog.jetbrains.com/idea/2015/06/intellij-idea-15 -EAP-是开/