在我们的项目中,我们正在迁移到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);
要...
List<Integer> result2 = naturals.stream()
.map(n -> n * 2)
.collect(Collectors.toList());
使用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];
}
但它没有像Guava那样直接调试代码,实际上我找不到n * 2
转换。
有没有办法看到这种转换或轻松调试此代码的方法?
编辑:我添加了不同评论的答案并发布了答案
感谢Holger
评论回答了我的问题,使用lambda阻塞的方法让我看到了转换过程并调试了lambda体内发生的事情:
.map(
n -> {
Integer nr = n * 2;
return nr;
}
)
感谢Stuart Marks
方法引用的方法也允许我调试转换过程:
static int timesTwo(int n) {
Integer result = n * 2;
return result;
}
...
List<Integer> result2 = naturals.stream()
.map(Java8Test::timesTwo)
.collect(Collectors.toList());
...
感谢Marlon Bernardes
回答,我注意到我的Eclipse没有显示它应该是什么,而peek()的使用有助于显示结果。
答案 0 :(得分:64)
在使用Eclipse或IntelliJ IDEA时,我通常没有问题调试lambda表达式。只需设置断点并确保不检查整个lambda表达式(仅检查lambda体)。
另一种方法是使用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());
<强>更新强>
我认为你感到困惑,因为map
是intermediate operation
- 换句话说:它是一个懒惰的操作,只有在terminal operation
执行后才会执行。所以当你打电话给stream.map(n -> n * 2)
时,lambda身体此刻并没有被执行。您需要设置断点并在调用终端操作后检查它(在这种情况下为collect
)。
检查Stream Operations以获取进一步说明。
更新2:
引用Holger's评论:
这里令人狡猾的是调用map和lambda 表达式在一行中,因此行断点将停止在两行上 完全无关的行动。
在
map(
之后插入换行符 允许你只为lambda表达式设置一个断点。 并且调试器不显示中间值并不罕见 一个return
声明。将lambda更改为n -> { int result=n * 2; return result; }
会允许你检查结果。再次,插入行 逐行踩踏时适当地打破...
答案 1 :(得分:23)
IntelliJ为这种情况提供了一个非常好的插件作为 Java Stream Debugger 插件。你应该看一下:https://plugins.jetbrains.com/plugin/9696-java-stream-debugger?platform=hootsuite
它通过添加Trace Current Stream Chain按钮扩展了IDEA调试器工具窗口,该按钮在调试器停止在Stream API调用链中时变为活动状态。
它有一个很好的界面来处理单独的流操作,并让你有机会遵循你应该调试的一些值。
您可以通过单击此处从“调试”窗口手动启动它:
答案 2 :(得分:19)
调试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());
}
这可能会让您在调试时更容易看到正在发生的事情。此外,以这种方式提取方法使单元测试更容易。如果你的lambda非常复杂,你需要单步执行它,那么你可能想要为它进行一系列的单元测试。
答案 3 :(得分:6)
Intellij IDEA 15似乎让它变得更加容易,它允许停在lambda所在的行的一部分,看到第一个特征:http://blog.jetbrains.com/idea/2015/06/intellij-idea-15-eap-is-open/
答案 4 :(得分:2)
仅为提供更多更新的细节(2019年10月),IntelliJ添加了一个非常不错的集成来调试这种类型的代码,这非常有用。
当我们按 F7 进入包含lambda的行(进入)时,IntelliJ将突出显示要调试的代码段。我们可以使用 Tab 切换要调试的块,一旦确定,就再次单击 F7 。
这里有一些截图说明了
答案 5 :(得分:0)
使用IDE进行调试总是很有帮助的,但是通过流中的每个元素进行调试的理想方式是在终端方法操作之前使用peek(),因为对Java Steams的评估是延迟的,因此除非调用终端方法,否则各自的流将不会被评估。
List<Integer> numFromZeroToTen = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
numFromZeroToTen.stream()
.map(n -> n * 2)
.peek(n -> System.out.println(n))
.collect(Collectors.toList());