Làm cách nào để gỡ lỗi stream (). Map (…) với các biểu thức lambda?


114

Trong dự án của chúng tôi, chúng tôi đang chuyển sang java 8 và chúng tôi đang thử nghiệm các tính năng mới của nó.

Trong dự án của mình, tôi đang sử dụng các chức năng và vị ngữ Guava để lọc và chuyển đổi một số bộ sưu tập bằng cách sử dụng Collections2.transformCollections2.filter.

Trong lần di chuyển này, tôi cần thay đổi mã lệnh ví dụ ổi thành java 8 thay đổi. Vì vậy, những thay đổi tôi đang làm là:

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

Đến...

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

Sử dụng ổi, tôi rất có thể gỡ lỗi mã vì tôi có thể gỡ lỗi từng quá trình chuyển đổi nhưng mối quan tâm của tôi là làm thế nào để gỡ lỗi chẳng hạn .map(n -> n*2).

Sử dụng trình gỡ lỗi, tôi có thể thấy một số mã như:

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

Nhưng nó không đơn giản như Guava để gỡ lỗi mã, thực sự tôi không thể tìm thấy sự n * 2chuyển đổi.

Có cách nào để xem sự chuyển đổi này hoặc một cách dễ dàng gỡ lỗi mã này không?

CHỈNH SỬA: Tôi đã thêm câu trả lời từ các nhận xét khác nhau và câu trả lời đã đăng

Cảm ơn Holgernhận xét đã trả lời câu hỏi của tôi, phương pháp sử dụng khối lambda cho phép tôi xem quá trình chuyển đổi và gỡ lỗi những gì đã xảy ra bên trong thân lambda:

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

Nhờ Stuart Markscách tiếp cận có các tham chiếu phương thức cũng cho phép tôi gỡ lỗi quá trình chuyển đổi:

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

Cảm ơn Marlon Bernardescâu trả lời, tôi nhận thấy rằng Eclipse của tôi không hiển thị những gì nó nên và việc sử dụng peek () đã giúp hiển thị kết quả.


Bạn không cần phải khai báo resultbiến tạm thời của mình là Integer. Một việc đơn giản intcũng nên làm nếu bạn đang mapping inttới một int
Holger

Tôi cũng nói thêm rằng có trình gỡ lỗi được cải tiến trong IntelliJ IDEA 14. Bây giờ chúng ta có thể gỡ lỗi Lamdas.
Mikhail

Câu trả lời:


86

Tôi thường không gặp vấn đề gì khi gỡ lỗi biểu thức lambda khi sử dụng Eclipse hoặc IntelliJ IDEA. Chỉ cần đặt một điểm ngắt và đảm bảo không kiểm tra toàn bộ biểu thức lambda (chỉ kiểm tra phần thân lambda).

Gỡ lỗi Lambdas

Một cách tiếp cận khác là sử dụng peekđể kiểm tra các phần tử của luồng:

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

CẬP NHẬT:

Tôi nghĩ bạn đang bối rối vì mapintermediate operation- nói cách khác: nó là một hoạt động lười biếng sẽ chỉ được thực thi sau khi một terminal operationđược thực thi. Vì vậy, khi bạn gọi stream.map(n -> n * 2)lambda body hiện không được thực thi. Bạn cần đặt một điểm ngắt và kiểm tra nó sau khi thao tác đầu cuối được gọi ( collect, trong trường hợp này).

Kiểm tra Hoạt động Luồng để được giải thích thêm.

CẬP NHẬT 2:

Trích dẫn nhận xét của Holger :

Điều làm cho nó trở nên phức tạp ở đây là lệnh gọi ánh xạ và biểu thức lambda nằm trên một dòng nên một điểm ngắt dòng sẽ dừng lại trên hai hành động hoàn toàn không liên quan.

Chèn một dấu ngắt dòng ngay sau map( đó sẽ cho phép bạn đặt dấu ngắt chỉ cho biểu thức lambda. Và không có gì lạ khi trình gỡ lỗi không hiển thị các giá trị trung gian của một returncâu lệnh. Thay đổi lambda thành n -> { int result=n * 2; return result; } sẽ cho phép bạn kiểm tra kết quả. Một lần nữa, hãy chèn các ngắt dòng một cách thích hợp khi bước từng dòng…


Cảm ơn cho màn hình in. Bạn có phiên bản Eclipse nào hoặc bạn đã làm gì để có được hộp thoại đó? Tôi đã thử sử dụng inspectdisplay và nhận được n cannot be resolved to a variable. Btw, peek cũng hữu ích nhưng in tất cả các giá trị cùng một lúc. Tôi muốn xem từng quá trình lặp lại để kiểm tra sự chuyển đổi. Nó có khả thi không?
Federico Piazza,

Tôi đang sử dụng Eclipse Kepler SR2 (với hỗ trợ Java 8 được cài đặt từ thị trường của Eclipse).
Marlon Bernardes

Bạn cũng đang sử dụng Eclipse? Chỉ cần đặt một điểm ngắt trên .mapdòng và nhấn F8 nhiều lần.
Marlon Bernardes

6
@Fede: điều làm cho nó phức tạp ở đây là lệnh gọi đến mapvà biểu thức lambda nằm trên một dòng nên một điểm ngắt dòng sẽ dừng lại trên hai hành động hoàn toàn không liên quan. Chèn một dấu ngắt dòng ngay sau map(đó sẽ cho phép bạn đặt dấu ngắt chỉ cho biểu thức lambda. Và không có gì lạ khi trình gỡ lỗi không hiển thị các giá trị trung gian của một returncâu lệnh. Thay đổi lambda thành n -> { int result=n * 2; return result; }sẽ cho phép bạn kiểm tra result. Một lần nữa, hãy chèn các ngắt dòng một cách thích hợp khi bước từng dòng…
Holger

1
@Marlon Bernardes: chắc chắn, bạn có thể thêm nó vào câu trả lời vì đó là mục đích của nhận xét: giúp cải thiện nội dung. Btw., Tôi đã chỉnh sửa văn bản được trích dẫn bằng cách thêm định dạng mã…
Holger

33

IntelliJ có một plugin rất hay cho trường hợp này là plugin Java Stream Debugger . Bạn nên kiểm tra: https://plugins.jetbrains.com/plugin/9696-java-stream-debugger?platform=hootsuite

Nó mở rộng cửa sổ công cụ IDEA Debugger bằng cách thêm nút Trace Current Stream Chain, nút này sẽ hoạt động khi trình gỡ lỗi dừng bên trong chuỗi lệnh gọi Stream API.

Nó có giao diện đẹp để làm việc với các hoạt động luồng riêng biệt và cho bạn cơ hội theo dõi một số giá trị mà bạn nên gỡ lỗi.

Trình gỡ lỗi dòng Java

Bạn có thể khởi chạy nó theo cách thủ công từ cửa sổ Gỡ lỗi bằng cách nhấp vào đây:

nhập mô tả hình ảnh ở đây


23

Gỡ lỗi lambdas cũng hoạt động tốt với NetBeans. Tôi đang sử dụng NetBeans 8 và JDK 8u5.

Nếu bạn đặt một điểm ngắt trên một dòng có lambda, bạn sẽ thực sự nhấn một lần khi đường ống được thiết lập và sau đó một lần cho mỗi phần tử luồng. Sử dụng ví dụ của bạn, lần đầu tiên bạn đạt đến điểm ngắt sẽ là lệnh map()gọi đang thiết lập đường dẫn luồng:

điểm dừng đầu tiên

Bạn có thể thấy ngăn xếp cuộc gọi và các biến cục bộ và giá trị tham số mainnhư bạn mong đợi. Nếu bạn tiếp tục bước, điểm ngắt "giống nhau" lại bị đánh trúng, ngoại trừ lần này nó nằm trong lệnh gọi lambda:

nhập mô tả hình ảnh ở đây

Lưu ý rằng lần này ngăn xếp cuộc gọi nằm sâu bên trong máy luồng và các biến cục bộ là cục bộ của chính lambda, không phải là mainphương thức bao . (Tôi đã thay đổi các giá trị trong naturalsdanh sách để làm rõ điều này.)

Như Marlon Bernardes đã chỉ ra (+1), bạn có thể sử dụng peekđể kiểm tra các giá trị khi chúng đi qua đường ống. Tuy nhiên, hãy cẩn thận nếu bạn đang sử dụng nó từ một luồng song song. Các giá trị có thể được in theo thứ tự không thể đoán trước trên các chuỗi khác nhau. Nếu bạn đang lưu trữ các giá trị trong cấu trúc dữ liệu gỡ lỗi từ peek, cấu trúc dữ liệu đó tất nhiên sẽ phải an toàn theo chuỗi.

Cuối cùng, nếu bạn đang thực hiện gỡ lỗi nhiều lambdas (đặc biệt là lambdas câu lệnh nhiều dòng), bạn nên trích xuất lambda thành một phương thức được đặt tên và sau đó tham chiếu nó bằng cách sử dụng tham chiếu phương thức. Ví dụ,

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

Điều này có thể giúp bạn dễ dàng xem những gì đang xảy ra trong khi gỡ lỗi. Ngoài ra, các phương pháp giải nén theo cách này giúp kiểm thử đơn vị dễ dàng hơn. Nếu lambda của bạn phức tạp đến mức bạn cần phải thực hiện từng bước một, có thể bạn muốn có một loạt các bài kiểm tra đơn vị cho nó.


Vấn đề của tôi là tôi không thể gỡ lỗi phần thân lambda nhưng cách tiếp cận sử dụng tham chiếu phương thức của bạn đã giúp tôi rất nhiều với những gì tôi muốn. Bạn có thể cập nhật câu trả lời của mình bằng cách sử dụng phương pháp Holger cũng hoạt động hoàn hảo bằng cách thêm { int result=n * 2; return result; }vào các dòng khác nhau và tôi có thể chấp nhận câu trả lời vì cả hai câu trả lời đều hữu ích. Tất nhiên là +1.
Federico Piazza

1
@Fede Có vẻ như câu trả lời khác đã được cập nhật, vì vậy không cần cập nhật câu trả lời của tôi. Dù sao thì tôi cũng ghét lambdas nhiều dòng. :-)
Stuart Marks

1
@Stuart Marks: Tôi cũng thích lambdas một dòng hơn. Vì vậy, tôi thường loại bỏ các dấu ngắt dòng sau khi gỡ lỗi ⟨điều này cũng áp dụng cho các câu lệnh ghép (thông thường) khác⟩.
Holger

1
@Fede Không phải lo lắng. Đó là đặc quyền của bạn khi người hỏi chấp nhận bất kỳ câu trả lời nào bạn thích. Cảm ơn vì +1.
Stuart Marks

1
Tôi nghĩ rằng việc tạo các tham chiếu phương thức, bên cạnh việc làm cho các phương pháp dễ dàng hơn để kiểm tra đơn vị cũng giúp mã dễ đọc hơn. Câu trả lời chính xác! (+1)
Marlon Bernardes


6

Chỉ để cung cấp thêm chi tiết cập nhật (tháng 10 năm 2019), IntelliJ đã thêm một tích hợp khá đẹp để gỡ lỗi loại mã cực kỳ hữu ích này.

Khi chúng ta dừng lại ở một dòng chứa lambda nếu chúng ta nhấn F7(bước vào) thì IntelliJ sẽ tô sáng đoạn mã cần gỡ lỗi. Chúng tôi có thể chuyển đổi đoạn mã cần gỡ lỗi Tabvà sau khi chúng tôi quyết định nó thì chúng tôi nhấp F7lại.

Dưới đây là một số ảnh chụp màn hình để minh họa:

1- Nhấn phím F7(bước vào), sẽ hiển thị các điểm nổi bật (hoặc chế độ lựa chọn) nhập mô tả hình ảnh ở đây

2- Sử dụng Tabnhiều lần để chọn đoạn mã cần gỡ lỗi nhập mô tả hình ảnh ở đây

3- Nhấn phím F7(bước vào) để bước vào nhập mô tả hình ảnh ở đây


1

Gỡ lỗi bằng cách sử dụng IDE luôn hữu ích, nhưng cách lý tưởng để gỡ lỗi qua từng phần tử trong một luồng là sử dụng peek () trước một hoạt động của phương thức đầu cuối vì Java Steams được đánh giá một cách lười biếng, vì vậy trừ khi một phương thức đầu cuối được gọi, luồng tương ứng sẽ không được đánh giá.

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());
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.