web-dev-qa-db-ja.com

ラムダ式でstream()。map(...)をデバッグする方法は?

このプロジェクトでは、Java 8に移行しており、その新しい機能をテストしています。

私のプロジェクトでは、Collections2.transformおよびCollections2.filterを使用していくつかのコレクションをフィルターおよび変換するために、Guavaの述語と関数を使用しています。

この移行では、たとえば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());

グアバを使用すると、各変換プロセスをデバッグできたため、コードのデバッグが非常に快適でしたが、私の懸念は、たとえば.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];
}

しかし、コードをデバッグするのはグアバほど簡単ではありません。実際、n * 2変換は見つかりませんでした。

この変換を確認する方法や、このコードを簡単にデバッグする方法はありますか?

編集:さまざまなコメントから回答を追加し、回答を投稿しました

私の質問に答えたHolgerコメントのおかげで、ラムダブロックを持つというアプローチにより、変換プロセスを確認し、ラムダ本体の内部で何が起こったかをデバッグできました。

.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 answerのおかげで、Eclipseに必要な内容が表示されず、peek()の使用が結果の表示に役立つことに気付きました。

97
Federico Piazza

通常、EclipseまたはIntelliJ IDEAを使用しているときに、ラムダ式のデバッグに問題はありません。ブレークポイントを設定し、ラムダ式全体を検査しないようにしてください(ラムダ本体のみを検査します)。

Debugging 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());

UPDATE:

mapintermediate operationであるため、混乱していると思います。つまり、terminal operationが実行された後にのみ実行される遅延操作です。したがって、stream.map(n -> n * 2)を呼び出すと、ラムダ本体は現在実行されていません。端末操作が呼び出された後、ブレークポイントを設定して検査する必要があります(この場合はcollect)。

詳細については、 Stream Operations を確認してください。

更新2:

引用 Holger's コメント:

ここで難しいのは、mapへの呼び出しとラムダ式が1行であるため、2つの完全に無関係なアクションで行ブレークポイントが停止することです。

map(の直後に改行を挿入すると、ラムダ式のみにブレークポイントを設定できます。また、デバッガーがreturnステートメントの中間値を表示しないことも珍しくありません。ラムダをn -> { int result=n * 2; return result; }に変更すると、結果を検査できます。繰り返しますが、1行ずつステップ実行するときに適切に改行を挿入します…

71

IntelliJには、この場合のJava Stream DebuggerプラグインなどのNiceプラグインがあります。あなたはそれをチェックアウトする必要があります: https://plugins.jetbrains.com/plugin/9696-Java-stream-debugger?platform=hootsuite

[現在のストリームチェーンをトレース]ボタンを追加することにより、IDEAデバッガツールウィンドウを拡張します。これは、デバッガーがストリームAPI呼び出しのチェーン内で停止したときにアクティブになります。

個別のストリーム操作を操作するための素敵なインターフェイスがあり、デバッグする必要があるいくつかの値を追跡する機会を提供します。

Java Stream Debugger

ここをクリックして、デバッグウィンドウから手動で起動できます。

enter image description here

25

ラムダのデバッグもNetBeansでうまく機能します。 NetBeans 8とJDK 8u5を使用しています。

ラムダがある行にブレークポイントを設定すると、パイプラインのセットアップ時に実際に1回ヒットし、次にストリーム要素ごとに1回ヒットします。あなたの例を使用すると、ブレークポイントに最初にヒットするのは、ストリームパイプラインを設定するmap()呼び出しです。

first breakpoint

予想どおり、コールスタックとmainのローカル変数とパラメーター値を確認できます。ステップ実行を続けると、「同じ」ブレークポイントが再びヒットしますが、今回はラムダの呼び出し内にあります。

enter image description here

今回は、コールスタックがストリームマシンの奥深くにあり、ローカル変数はラムダ自体のローカルであり、外側のmainメソッドではないことに注意してください。 (これを明確にするためにnaturalsリストの値を変更しました。)

Marlon Bernardes 指摘(+1)のように、peekを使用して、パイプラインを通過する値を検査できます。ただし、並列ストリームからこれを使用している場合は注意してください。値は、異なるスレッド間で予測不能な順序で印刷できます。 peekのデバッグデータ構造に値を保存している場合、そのデータ構造はもちろんスレッドセーフである必要があります。

最後に、ラムダ(特に複数行のステートメントラムダ)のデバッグを多数行う場合は、ラムダを名前付きメソッドに抽出し、メソッド参照を使用して参照することをお勧めします。例えば、

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());
}

これにより、デバッグ中に何が起こっているかを簡単に確認できます。さらに、この方法でメソッドを抽出すると、単体テストが簡単になります。ラムダが非常に複雑であるため、シングルステップを実行する必要がある場合は、とにかく大量の単体テストが必要になります。

21
Stuart Marks

Intellij IDEA 15を使用するとさらに簡単になり、ラムダが存在する行の一部で停止できます。最初の機能を参照してください。 http://blog.jetbrains.com/idea/2015/06/intellij-idea-15-eap-is-open /

6
Egor