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

115

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

Используя гуаву, мне было очень удобно отлаживать код, так как я мог отлаживать каждый процесс преобразования, но меня беспокоит, например, как отлаживать .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преобразование.

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

РЕДАКТИРОВАТЬ: я добавил ответ из разных комментариев и опубликовал ответы

Благодаря 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также должно подойти, если вы отправляете mapпинг intна int
Хольгер
Также я добавляю, что в IntelliJ IDEA 14 есть улучшенный отладчик. Теперь мы можем отлаживать Lamdas.
Михаил

Ответы:

86

Обычно у меня нет проблем с отладкой лямбда-выражений при использовании Eclipse или IntelliJ IDEA. Просто установите точку останова и не проверяйте все лямбда-выражение (проверяйте только тело лямбда).

Отладка лямбда-выражений

Другой подход - использовать 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());

ОБНОВИТЬ:

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

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

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

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

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

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

Марлон Бернардес
источник
Спасибо за экран для печати. Какая у вас версия Eclipse или что вы сделали, чтобы получить этот диалог? Я пробовал использовать inspectи display и получить n cannot be resolved to a variable. Кстати, peek тоже полезен, но выводит все значения сразу. Я хочу видеть каждую итерацию, чтобы проверить преобразование. Возможно ли это?
Федерико Пьяцца
Я использую Eclipse Kepler SR2 (с поддержкой Java 8, установленной с торговой площадки Eclipse).
Марлон Бернардес
Вы тоже используете Eclipse? Просто установите точку останова в .mapстроке и нажмите F8 несколько раз.
Марлон Бернардес
6
@Fede: что делает здесь сложным, так это то, что вызов mapи лямбда-выражение находятся в одной строке, поэтому точка останова на строке остановится на двух совершенно не связанных действиях. Вставка разрыва строки сразу после map(позволит вам установить точку останова только для лямбда-выражения. И нет ничего необычного в том, что отладчики не показывают промежуточные значения returnоператора. Изменение лямбды наn -> { int result=n * 2; return result; } позволит вам проверить result. Опять же, вставляйте разрывы строк соответствующим образом при переходе по строкам…
Holger
1
@Marlon Bernardes: конечно, вы можете добавить это к ответу, поскольку цель комментариев: помочь улучшить контент. Кстати, я отредактировал цитируемый текст, добавив форматирование кода…
Хольгер
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

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

введите описание изображения здесь

Дмитрий Мельничук
источник
23

Отладка лямбда-выражений также хорошо работает с NetBeans. Я использую NetBeans 8 и JDK 8u5.

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

первая точка останова

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

введите описание изображения здесь

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

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

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

Стюарт Маркс
источник
Моя проблема заключалась в том, что я не мог отладить тело лямбда, но ваш подход к использованию ссылок на методы очень помог мне с тем, что я хотел. Вы можете обновить свой ответ, используя подход Хольгера, который также отлично работал, добавляя { int result=n * 2; return result; }разные строки, и я мог принять ответ, поскольку оба ответа были полезны. +1 конечно.
Federico Piazza
1
@Fede Похоже, что другой ответ уже обновлен, поэтому нет необходимости обновлять мой. Я все равно ненавижу многострочные лямбды. :-)
Стюарт Маркс
1
@Stuart Marks: Я тоже предпочитаю однострочные лямбды. Поэтому обычно я удаляю разрывы строк после отладки, «что применимо и к другим (обычным) составным операторам».
Holger
1
@Fede Не беспокойся. Это ваша прерогатива как спрашивающего - принять тот ответ, который вам больше нравится. Спасибо за +1.
Стюарт Маркс
1
Я думаю, что создание ссылок на методы, помимо упрощения методов для модульных тестов, также делает код более читаемым. Отличный ответ! (+1)
Марлон Бернардес
6

Чтобы предоставить более подробную информацию (октябрь 2019 г.), IntelliJ добавил довольно хорошую интеграцию для отладки этого типа кода, который чрезвычайно полезен.

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

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

1- Нажмите кнопку F7(перейти), отобразятся основные моменты (или режим выбора) введите описание изображения здесь

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

3- Нажмите кнопку F7(шаг в), чтобы войти в введите описание изображения здесь

Федерико Пьяцца
источник
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());
Сашанк Самантрай
источник