วิธีการดีบักสตรีม () แผนที่ (…) ด้วยนิพจน์แลมบ์ดา


115

ในโครงการของเราเรากำลังย้ายไปที่ java 8 และเรากำลังทดสอบคุณสมบัติใหม่ของมัน

ในโครงการของฉันฉันใช้ภาคฝรั่งและฟังก์ชั่นในการกรองและการแปลงคอลเลกชันโดยใช้และCollections2.transformCollections2.filter

ในการย้ายข้อมูลนี้ฉันจำเป็นต้องเปลี่ยนเช่น guava code เป็น 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คำตอบฉันสังเกตเห็นว่า Eclipse ของฉันไม่แสดงสิ่งที่ควรและการใช้ peek () ช่วยในการแสดงผลลัพธ์


คุณไม่จำเป็นต้องประกาศresultตัวแปรชั่วคราวของคุณเป็นInteger. สิ่งง่ายๆintควรทำได้เช่นกันหากคุณกำลังmapping intไปที่int...
Holger

นอกจากนี้ฉันยังเพิ่มว่ามีดีบักเกอร์ที่ปรับปรุงแล้วใน IntelliJ IDEA 14 ตอนนี้เราสามารถดีบัก Lamdas ได้แล้ว
Mikhail

คำตอบ:


86

ฉันมักจะไม่มีปัญหาในการดีบักนิพจน์แลมบักในขณะที่ใช้ Eclipse หรือ IntelliJ IDEA เพียงตั้งจุดพักและอย่าตรวจสอบนิพจน์แลมด้าทั้งหมด (ตรวจสอบเฉพาะตัวแลมด้า)

การดีบัก 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:

ฉันคิดว่าคุณกำลังสับสนเพราะmapเป็นintermediate operation- กล่าวอีกนัยหนึ่งคือมันเป็นการดำเนินการที่ขี้เกียจซึ่งจะดำเนินการหลังจากterminal operationดำเนินการแล้วเท่านั้น ดังนั้นเมื่อคุณเรียกstream.map(n -> n * 2)ร่างกายแลมด้าจะไม่ถูกประหารชีวิตในขณะนี้ คุณต้องตั้งค่าเบรกพอยต์และตรวจสอบหลังจากเรียกใช้การดำเนินการเทอร์มินัลแล้ว ( collectในกรณีนี้)

ตรวจสอบการทำงานของสตรีมสำหรับคำอธิบายเพิ่มเติม

อัปเดต 2:

อ้างถึงความคิดเห็นของ Holger :

สิ่งที่ทำให้ยุ่งยากตรงนี้คือการเรียกไปยังแมปและการแสดงออกของแลมบ์ดาอยู่ในบรรทัดเดียวดังนั้นจุดพักบรรทัดจะหยุดการกระทำสองอย่างที่ไม่เกี่ยวข้องกันโดยสิ้นเชิง

การแทรกตัวแบ่งบรรทัดทันทีmap( จะช่วยให้คุณกำหนดจุดพักสำหรับนิพจน์แลมบ์ดาเท่านั้น และไม่ใช่เรื่องแปลกที่ดีบักเกอร์จะไม่แสดงค่ากลางของreturnคำสั่ง การเปลี่ยนแลมด้าเพื่อn -> { int result=n * 2; return result; } ให้คุณตรวจสอบผลลัพธ์ได้ อีกครั้งแทรกตัวแบ่งบรรทัดอย่างเหมาะสมเมื่อก้าวทีละบรรทัด ...


ขอบคุณสำหรับหน้าจอการพิมพ์ คุณมี Eclipse เวอร์ชันใดหรือทำอะไรเพื่อให้ได้กล่องโต้ตอบนั้น ฉันพยายามใช้inspectและและได้รับdisplay n cannot be resolved to a variableBtw, peek ก็มีประโยชน์เช่นกัน แต่จะพิมพ์ค่าทั้งหมดพร้อมกัน ฉันต้องการดูกระบวนการทำซ้ำแต่ละครั้งเพื่อตรวจสอบการเปลี่ยนแปลง เป็นไปได้หรือไม่?
Federico Piazza

ฉันใช้ Eclipse Kepler SR2 (พร้อมการสนับสนุน Java 8 ที่ติดตั้งจากตลาดของ Eclipse)
Marlon Bernardes

คุณใช้ Eclipse ด้วยหรือเปล่า? เพียงตั้งเบรกพอยต์บน.mapบรรทัดและกด F8 หลาย ๆ ครั้ง
Marlon Bernardes

6
@Fede: สิ่งที่ทำให้ยุ่งยากที่นี่คือการเรียกmapและการแสดงออกของแลมบ์ดาอยู่ในบรรทัดเดียวดังนั้นจุดพักบรรทัดจะหยุดการกระทำสองอย่างที่ไม่เกี่ยวข้องกันโดยสิ้นเชิง การแทรกตัวแบ่งบรรทัดทันทีmap(จะช่วยให้คุณกำหนดจุดพักสำหรับนิพจน์แลมบ์ดาเท่านั้น และไม่ใช่เรื่องแปลกที่ดีบักเกอร์จะไม่แสดงค่ากลางของreturnคำสั่ง การเปลี่ยนแลมด้าเพื่อn -> { int result=n * 2; return result; }ให้คุณตรวจสอบresultได้ อีกครั้งแทรกเส้นแบ่งอย่างเหมาะสมเมื่อก้าวทีละบรรทัด ...
Holger

1
@ Marlon Bernardes: แน่นอนว่าคุณสามารถเพิ่มคำตอบลงในคำตอบได้เพราะนั่นคือจุดประสงค์ของการแสดงความคิดเห็น: ช่วยปรับปรุงเนื้อหา ฉันได้แก้ไขข้อความที่ยกมาเพื่อเพิ่มการจัดรูปแบบรหัส ...
Holger

33

IntelliJ มีปลั๊กอินที่ดีสำหรับกรณีนี้เช่นปลั๊กอินJava Stream Debugger คุณควรตรวจสอบ: https://plugins.jetbrains.com/plugin/9696-java-stream-debugger?platform=hootsuite

มันขยายหน้าต่างเครื่องมือ IDEA Debugger โดยการเพิ่มปุ่ม Trace Current Stream Chain ซึ่งจะทำงานเมื่อดีบักเกอร์หยุดอยู่ภายในห่วงโซ่ของการเรียก Stream API

มีอินเทอร์เฟซที่ดีสำหรับการทำงานกับการดำเนินการสตรีมแยกต่างหากและเปิดโอกาสให้คุณปฏิบัติตามค่าบางอย่างที่คุณควรแก้ไขข้อบกพร่อง

Java Stream Debugger

คุณสามารถเปิดใช้งานได้ด้วยตนเองจากหน้าต่าง Debug โดยคลิกที่นี่:

ใส่คำอธิบายภาพที่นี่


23

การแก้จุดบกพร่อง lambdas ยังทำงานได้ดีกับ NetBeans ฉันใช้ NetBeans 8 และ JDK 8u5

หากคุณตั้งค่าเบรกพอยต์บนเส้นที่มีแลมบ์ดาคุณจะตีหนึ่งครั้งเมื่อตั้งค่าไปป์ไลน์แล้วหนึ่งครั้งสำหรับองค์ประกอบสตรีมแต่ละรายการ จากตัวอย่างของคุณครั้งแรกที่คุณกดเบรกพอยต์จะเป็นการmap()โทรที่ตั้งค่าไปป์ไลน์สตรีม:

จุดพักแรก

คุณสามารถดู call stack และตัวแปรภายในและค่าพารามิเตอร์ได้mainตามที่คุณคาดหวัง หากคุณก้าวต่อไปเบรกพอยต์ "เดียวกัน" จะถูกตีอีกครั้งยกเว้นในครั้งนี้จะอยู่ในการเรียกใช้แลมด้า

ใส่คำอธิบายภาพที่นี่

โปรดทราบว่าเวลานี้ call stack อยู่ลึกเข้าไปในเครื่องจักรของสตรีมและตัวแปรโลคัลคือโลคัลของแลมบ์ดาเองไม่ใช่mainวิธีการปิดล้อม (ฉันได้เปลี่ยนค่าในnaturalsรายการเพื่อให้ชัดเจน)

ดังที่Marlon Bernardesชี้ให้เห็น (+1) คุณสามารถใช้peekเพื่อตรวจสอบค่าต่างๆในขณะที่ไปป์ไลน์ โปรดระวังแม้ว่าคุณจะใช้สิ่งนี้จากสตรีมคู่ขนาน สามารถพิมพ์ค่าตามลำดับที่คาดเดาไม่ได้ในเธรดต่างๆ หากคุณกำลังจัดเก็บค่าในโครงสร้างข้อมูลการดีบักโครงสร้างpeekข้อมูลนั้นจะต้องปลอดภัยต่อเธรด

สุดท้ายหากคุณกำลังทำการดีบัก lambdas เป็นจำนวนมาก (โดยเฉพาะ lambdas คำสั่งหลายบรรทัด) อาจเป็นการดีกว่าที่จะแยกแลมบ์ดาเป็นเมธอดที่ตั้งชื่อแล้วอ้างถึงโดยใช้การอ้างอิงวิธีการ ตัวอย่างเช่น,

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

วิธีนี้อาจทำให้ง่ายขึ้นเพื่อดูว่าเกิดอะไรขึ้นในขณะที่คุณกำลังแก้ไขข้อบกพร่อง นอกจากนี้การแยกวิธีด้วยวิธีนี้ทำให้ง่ายต่อการทดสอบหน่วย หากแลมด้าของคุณมีความซับซ้อนมากจนคุณต้องก้าวผ่านไปทีละก้าวคุณอาจต้องการมีการทดสอบหลายหน่วยสำหรับมันอยู่ดี


ปัญหาของฉันคือฉันไม่สามารถดีบักเนื้อแลมด้าได้ แต่วิธีการใช้การอ้างอิงวิธีการของคุณช่วยฉันได้มากกับสิ่งที่ฉันต้องการ คุณสามารถอัปเดตคำตอบของคุณโดยใช้วิธีการของ Holger ที่ทำงานได้อย่างสมบูรณ์โดยการเพิ่ม{ int result=n * 2; return result; }ในบรรทัดที่แตกต่างกันและฉันสามารถยอมรับคำตอบได้เนื่องจากคำตอบทั้งสองมีประโยชน์ +1 แน่นอน
Federico Piazza

1
@Fede ดูเหมือนว่าคำตอบอื่นได้รับการอัปเดตแล้วดังนั้นไม่จำเป็นต้องอัปเดตของฉัน ฉันเกลียด lambdas หลายบรรทัดอยู่แล้ว :-)
Stuart Marks

1
@Stuart Marks: ฉันชอบ lambdas บรรทัดเดียวด้วย ดังนั้นโดยปกติฉันจะลบตัวแบ่งบรรทัดหลังจากการดีบัก⟨ซึ่งใช้กับคำสั่งผสมอื่น ๆ (ธรรมดา) ด้วย
Holger

1
@ เฟดไร้กังวล. เป็นสิทธิพิเศษของคุณในฐานะผู้ถามที่จะยอมรับคำตอบใดก็ได้ที่คุณต้องการ ขอบคุณสำหรับ +1
Stuart Marks

1
ฉันคิดว่าการสร้างการอ้างอิงเมธอดนอกจากทำให้เมธอดง่ายขึ้นในการทดสอบหน่วยแล้วยังทำให้โค้ดอ่านง่ายขึ้น ตอบโจทย์มาก! (+1)
Marlon Bernardes


6

เพื่อให้รายละเอียดที่อัปเดตเพิ่มเติม (ต.ค. 2019) IntelliJ ได้เพิ่มการผสานรวมที่ดีงามเพื่อดีบักโค้ดประเภทนี้ซึ่งมีประโยชน์อย่างยิ่ง

เมื่อเราหยุดที่บรรทัดที่มีแลมด้าถ้าเรากดF7(ก้าวเข้าสู่) IntelliJ จะไฮไลต์สิ่งที่จะเป็นตัวอย่างที่จะแก้ไขข้อบกพร่อง เราสามารถสลับสิ่งที่จะแก้จุดบกพร่องด้วยTabและเมื่อเราตัดสินใจแล้วเราก็คลิกF7อีกครั้ง

นี่คือภาพหน้าจอบางส่วนเพื่อแสดง:

1- กดปุ่มF7(เข้าสู่) จะแสดงไฮไลท์ (หรือโหมดการเลือก) ใส่คำอธิบายภาพที่นี่

2- ใช้Tabหลาย ๆ ครั้งเพื่อเลือกตัวอย่างข้อมูลที่จะแก้ไขข้อบกพร่อง ใส่คำอธิบายภาพที่นี่

3- กดปุ่มF7(step into) เพื่อเข้าสู่ ใส่คำอธิบายภาพที่นี่


1

การดีบักโดยใช้ 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());
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.