Как отлаживать поток(). Map (...) с лямбда-выражениями?

В нашем проекте мы переходим на 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.

Есть ли способ увидеть это преобразование или способ легко отладить этот код?

EDIT: я добавил ответ из разных комментариев и опубликовал ответы

Благодаря комментарию 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() помогло отображать результаты.

Ответ 1

У меня обычно нет проблем с отладкой лямбда-выражений при использовании 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:

Я думаю, вы запутались, потому что map - это intermediate operation - другими словами: это ленивая операция, которая будет выполняться только после выполнения terminal operation. Поэтому, когда вы вызываете stream.map(n -> n * 2), лямбда-тело в данный момент не выполняется. Вам необходимо установить точку останова и проверить ее после вызова операции терминала (в данном случае collect).

Проверьте Потоковые операции для дальнейших объяснений.

ОБНОВЛЕНИЕ 2:

Цитировать Хольгер комментарий:

Что делает это сложным, так это то, что вызов карты и лямбда выражение находится в одной строке, поэтому точка останова строки остановится на двух совершенно не связанные действия.

Вставка разрыва строки сразу после map( позволит вам установить точку останова только для лямбда-выражения. И нет ничего необычного в том, что отладчики не показывают промежуточные значения return выражение. Изменение лямбды на n -> { int result=n * 2; return result; } позволит вам проверить результат. Снова вставьте строку перерывы соответственно, шагая строка за строкой...

Ответ 2

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

Вы можете запустить его вручную из окна отладки, нажав здесь:

enter image description here

Ответ 3

Отладка lambdas также хорошо работает с NetBeans. Я использую NetBeans 8 и JDK 8u5.

Если вы установите точку останова на линии, где есть лямбда, вы нажмете один раз, когда конвейер настроен, а затем один раз для каждого элемента потока. Используя ваш пример, при первом нажатии на точку останова будет вызов map(), который настраивает конвейер потока:

first breakpoint

Вы можете увидеть стек вызовов и локальные переменные и значения параметров для main, как и следовало ожидать. Если вы продолжите шаг, то "одна и та же" точка останова будет снова удалена, за исключением того, что она будет в пределах вызова лямбда:

enter image description here

Обратите внимание, что на этот раз стек вызовов находится глубоко внутри механизма потоков, а локальные переменные - это локали самой лямбда, а не охватывающий метод main. (Я изменил значения в списке naturals, чтобы сделать это ясно.)

Как Марлон Бернардес указал (+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());
}

Это может упростить просмотр того, что происходит во время отладки. Кроме того, извлечение методов таким образом облегчает unit test. Если ваша лямбда настолько сложна, что вам нужно пройти ее через один шаг, вы, вероятно, захотите иметь кучу единичных тестов для нее.

Ответ 5

Просто чтобы предоставить более обновленную информацию, IntelliJ добавил довольно приятную интеграцию для отладки такого типа кода, который чрезвычайно полезен.

Когда мы остановимся на строке, содержащей лямбду, если мы нажмем F7 (шаг в), то IntelliJ выделит то, что будет фрагментом для отладки. Мы можем переключить какой блок для отладки с помощью Tab, и как только мы решили это, мы снова нажимаем F7.

Вот несколько скриншотов для иллюстрации:

1- Нажмите клавишу F7 (шаг в), отобразятся основные моменты (или режим выбора) enter image description here

2- Используйте Tab несколько раз, чтобы выбрать фрагмент для отладки. enter image description here

3- Нажмите F7 (шаг в), чтобы войти в enter image description here