Bagaimana cara men-debug stream (). Map (…) dengan ekspresi lambda?

114

Dalam proyek kami, kami bermigrasi ke java 8 dan kami menguji fitur-fitur baru itu.

Pada proyek saya, saya menggunakan predikat dan fungsi Jambu biji untuk memfilter dan mengubah beberapa koleksi menggunakan Collections2.transformdan Collections2.filter.

Pada migrasi ini saya perlu mengubah misalnya kode jambu biji ke java 8 perubahan. Jadi, perubahan yang saya lakukan adalah seperti:

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

Untuk...

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

Menggunakan jambu biji saya sangat nyaman men-debug kode karena saya dapat men-debug setiap proses transformasi tetapi perhatian saya adalah bagaimana men-debug misalnya .map(n -> n*2).

Menggunakan debugger saya dapat melihat beberapa kode seperti:

@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];
}

Tetapi tidak sesulit Jambu untuk men-debug kode, sebenarnya saya tidak dapat menemukan n * 2transformasinya.

Adakah cara untuk melihat transformasi ini atau cara untuk men-debug kode ini dengan mudah?

EDIT: Saya telah menambahkan jawaban dari komentar yang berbeda dan jawaban diposting

Berkat Holgerkomentar yang menjawab pertanyaan saya, pendekatan memiliki blok lambda memungkinkan saya untuk melihat proses transformasi dan men-debug apa yang terjadi di dalam tubuh lambda:

.map(
    n -> {
        Integer nr = n * 2;
        return nr;
    }
)

Berkat Stuart Markspendekatan memiliki referensi metode juga memungkinkan saya untuk men-debug proses transformasi:

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

Berkat Marlon Bernardesjawaban saya perhatikan bahwa Eclipse saya tidak menunjukkan apa yang seharusnya dan penggunaan peek () membantu menampilkan hasil.

Federico Piazza
sumber
Anda tidak perlu mendeklarasikan resultvariabel sementara Anda sebagai Integer. Yang sederhana intharus dilakukan juga jika Anda melakukan mapping intke int...
Holger
Saya juga menambahkan bahwa ada debugger yang ditingkatkan di IntelliJ IDEA 14. Sekarang kita dapat men-debug Lamdas.
Mikhail

Jawaban:

86

Saya biasanya tidak memiliki masalah debugging ekspresi lambda saat menggunakan Eclipse atau IntelliJ IDEA. Cukup tetapkan breakpoint dan pastikan untuk tidak memeriksa seluruh ekspresi lambda (hanya memeriksa isi lambda).

Debugging Lambdas

Pendekatan lain yang digunakan peekuntuk memeriksa elemen aliran:

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

MEMPERBARUI:

Saya pikir Anda semakin bingung karena mapmerupakan intermediate operation- dengan kata lain: ini adalah operasi malas yang akan dijalankan hanya setelah terminal operationdieksekusi. Jadi saat Anda memanggil stream.map(n -> n * 2)tubuh lambda tidak sedang dijalankan saat ini. Anda perlu menyetel breakpoint dan memeriksanya setelah operasi terminal dipanggil ( collect, dalam kasus ini).

Periksa Operasi Stream untuk penjelasan lebih lanjut.

UPDATE 2:

Mengutip komentar Holger :

Yang membuatnya rumit di sini adalah bahwa panggilan untuk memetakan dan ekspresi lambda berada dalam satu baris sehingga titik henti sementara baris akan berhenti pada dua tindakan yang sama sekali tidak terkait.

Memasukkan jeda baris tepat setelahnya map( akan memungkinkan Anda menetapkan titik putus hanya untuk ekspresi lambda. Dan bukan hal yang aneh jika debugger tidak menampilkan nilai perantara dari sebuah returnpernyataan. Mengubah lambda menjadi n -> { int result=n * 2; return result; } akan memungkinkan Anda untuk memeriksa hasilnya. Sekali lagi, sisipkan jeda baris dengan tepat saat melangkah baris demi baris…

Marlon Bernardes
sumber
Terima kasih untuk layar cetaknya. Versi Eclipse apa yang Anda miliki atau apa yang Anda lakukan untuk mendapatkan dialog itu? Saya mencoba menggunakan inspectdan display dan mendapatkan n cannot be resolved to a variable. Btw, intip juga berguna tetapi mencetak semua nilai sekaligus. Saya ingin melihat setiap proses iterasi untuk memeriksa transformasi. Apakah itu memungkinkan?
Federico Piazza
Saya menggunakan Eclipse Kepler SR2 (dengan dukungan Java 8 diinstal dari pasar Eclipse).
Marlon Bernardes
Apakah Anda juga menggunakan Eclipse? Cukup atur breakpoint pada .mapbaris dan tekan F8 beberapa kali.
Marlon Bernardes
6
@Fede: yang membuatnya rumit di sini adalah bahwa panggilan ke mapdan ekspresi lambda berada dalam satu baris sehingga titik henti sementara baris akan berhenti pada dua tindakan yang sama sekali tidak terkait. Memasukkan jeda baris tepat setelahnya map(akan memungkinkan Anda menetapkan titik henti hanya untuk ekspresi lambda. Dan bukan hal yang aneh jika debugger tidak menampilkan nilai perantara dari sebuah returnpernyataan. Mengubah lambda menjadi n -> { int result=n * 2; return result; }akan memungkinkan Anda untuk memeriksanya result. Sekali lagi, sisipkan jeda baris dengan tepat saat melangkah baris demi baris…
Holger
1
@Marlon Bernardes: tentu, Anda dapat menambahkannya ke jawaban karena itulah tujuan komentar: membantu meningkatkan konten. Btw., Saya telah mengedit teks kutipan yang menambahkan pemformatan kode…
Holger
33

IntelliJ memiliki plugin yang bagus untuk kasus ini seperti plugin Java Stream Debugger . Anda harus memeriksanya: https://plugins.jetbrains.com/plugin/9696-java-stream-debugger?platform=hootsuite

Ini memperluas jendela alat Debugger IDEA dengan menambahkan tombol Trace Current Stream Chain, yang menjadi aktif ketika debugger berhenti di dalam rangkaian panggilan API Stream.

Ini memiliki antarmuka yang bagus untuk bekerja dengan operasi aliran terpisah dan memberi Anda kesempatan untuk mengikuti beberapa nilai yang harus Anda debug.

Java Stream Debugger

Anda dapat meluncurkannya secara manual dari jendela Debug dengan mengklik di sini:

masukkan deskripsi gambar di sini

Dmytro Melnychuk
sumber
23

Debugging lambda juga bekerja dengan baik dengan NetBeans. Saya menggunakan NetBeans 8 dan JDK 8u5.

Jika Anda menyetel breakpoint pada baris di mana terdapat lambda, Anda sebenarnya akan menekan sekali saat pipeline disiapkan, lalu sekali untuk setiap elemen aliran. Menggunakan contoh Anda, pertama kali Anda mencapai breakpoint adalah map()panggilan yang menyiapkan pipeline streaming:

breakpoint pertama

Anda dapat melihat tumpukan panggilan dan variabel lokal serta nilai parameter mainseperti yang Anda harapkan. Jika Anda terus melangkah, breakpoint yang "sama" dipukul lagi, kecuali kali ini dalam panggilan ke lambda:

masukkan deskripsi gambar di sini

Perhatikan bahwa kali ini tumpukan panggilan berada jauh di dalam mesin aliran, dan variabel lokalnya adalah lokal lambda itu sendiri, bukan mainmetode pelingkupan . (Saya telah mengubah nilai dalam naturalsdaftar untuk memperjelas ini.)

Seperti yang ditunjukkan oleh Marlon Bernardes (+1), Anda dapat menggunakan peekuntuk memeriksa nilai saat nilai berjalan di dalam pipeline. Berhati-hatilah jika Anda menggunakan ini dari aliran paralel. Nilainya dapat dicetak dalam urutan tak terduga di berbagai utas. Jika Anda menyimpan nilai dalam struktur data debugging dari peek, struktur data tersebut tentu saja harus aman untuk thread.

Terakhir, jika Anda melakukan banyak debugging lambda (terutama lambda pernyataan multi-baris), mungkin lebih baik untuk mengekstrak lambda ke dalam metode bernama dan kemudian merujuknya menggunakan referensi metode. Sebagai contoh,

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

Ini mungkin mempermudah untuk melihat apa yang terjadi saat Anda melakukan debug. Selain itu, mengekstraksi metode dengan cara ini mempermudah pengujian unit. Jika lambda Anda sangat rumit sehingga Anda harus melewatinya satu langkah, Anda mungkin ingin melakukan banyak pengujian unit untuk itu.

Stuart Marks
sumber
Masalah saya adalah saya tidak dapat men-debug body lambda tetapi pendekatan Anda menggunakan referensi metode sangat membantu saya dengan apa yang saya inginkan. Anda dapat memperbarui jawaban Anda menggunakan pendekatan Holger yang juga berfungsi sempurna dengan menambahkan { int result=n * 2; return result; }baris yang berbeda dan saya dapat menerima jawabannya karena kedua jawaban itu membantu. +1 tentu saja.
Federico Piazza
1
@Fede Sepertinya jawaban lain sudah diperbarui, jadi tidak perlu memperbarui milik saya. Saya benci lambda multi-baris. :-)
Stuart Marks
1
@Stuart Marks: Saya lebih suka lambda satu baris juga. Jadi biasanya saya menghapus jeda baris setelah debugging ⟨yang juga berlaku untuk pernyataan gabungan (biasa) lainnya⟩.
Holger
1
@Fede Jangan khawatir. Merupakan hak prerogatif Anda sebagai penanya untuk menerima jawaban mana pun yang Anda sukai. Terima kasih telah memberi +1.
Stuart Marks
1
Menurut saya, membuat referensi metode, selain membuat metode lebih mudah untuk pengujian unit juga membuat kode lebih mudah dibaca. Jawaban yang bagus! (+1)
Marlon Bernardes
6

Hanya untuk memberikan detail yang lebih baru (Okt 2019), IntelliJ telah menambahkan integrasi yang cukup bagus untuk men-debug jenis kode yang sangat berguna ini.

Ketika kita berhenti di baris yang berisi lambda jika kita menekan F7(melangkah ke) maka IntelliJ akan menyorot apa yang akan menjadi potongan untuk debug. Kita dapat mengganti potongan apa yang akan didebug Tabdan setelah kita memutuskannya maka kita klik F7lagi.

Berikut beberapa tangkapan layar untuk diilustrasikan:

1- Tekan F7(melangkah ke), akan menampilkan sorotan (atau mode pemilihan) masukkan deskripsi gambar di sini

2- Gunakan Tabbeberapa kali untuk memilih potongan yang akan di-debug masukkan deskripsi gambar di sini

3- Tekan F7tombol (melangkah ke) untuk masuk masukkan deskripsi gambar di sini

Federico Piazza
sumber
1

Debugging menggunakan IDE selalu membantu, tetapi cara ideal untuk men-debug melalui setiap elemen dalam aliran adalah dengan menggunakan peek () sebelum operasi metode terminal karena Java Steams dievaluasi secara malas, jadi kecuali metode terminal dipanggil, aliran masing-masing akan tidak dievaluasi.

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());
Sashank Samantray
sumber