Як налагодити stream (). Map (…) з лямбда-виразами?


114

У нашому проекті ми переходимо на 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 () допомогло відобразити результати.


Вам не потрібно оголошувати свою тимчасову resultзмінну як Integer. Простий intповинен зробити так само, якщо ви mapдобираєтесь intдо int
Holger

Також додаю, що в 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- іншими словами: це лінива операція, яка буде виконуватися лише після того, як буде виконано a terminal operation. Отже, коли ви телефонуєте, stream.map(n -> n * 2)тіло лямбда зараз не виконується. Потрібно встановити точку перерви та перевірити її після виклику роботи терміналу ( collectу цьому випадку).

Перевірте потокові операції для отримання додаткових пояснень.

ОНОВЛЕННЯ 2:

Цитуючи коментар Холгера :

Тут стає складним те, що виклик до відображення та вираз лямбда знаходяться в одному рядку, тому точка розриву лінії зупинятиметься на двох абсолютно не пов’язаних між собою дії.

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


Дякуємо за екран друку Яка версія Eclipse у вас є чи що ви зробили, щоб отримати цей діалог? Я спробував використовувати inspectі display і отримати n cannot be resolved to a variable. Btw, peek теж корисний, але друкує всі значення одразу. Я хочу бачити кожен процес ітерації, щоб перевірити трансформацію. Чи це можливо?
Федеріко Піацца

Я використовую Eclipse Kepler SR2 (з підтримкою Java 8, встановленою на ринку Eclipse).
Марлон Бернардес

Ви також використовуєте Eclipse? Просто встановіть точку перерви на .mapлінії та натисніть F8 кілька разів.
Марлон Бернардес

6
@Fede: тут стає складним те, що виклик mapта лямбда-вираз знаходяться в одному рядку, тому точка переривання лінії зупинятиметься на двох абсолютно непов'язаних діях. Вставлення розриву рядка відразу після map(дозволить вам встановити точку розриву лише для виразу лямбда. І це не незвично, що налагоджувачі не показують проміжних значень returnоператора. Зміна лямбда на n -> { int result=n * 2; return result; }дозволить вам оглянути result. Знову належним чином вставляйте розриви рядків, коли крокуєте по рядку…
Холгер

1
@Marlon Bernardes: впевнений, ви можете додати його у відповідь, оскільки це і є мета коментарів: допомогти покращити вміст. До речі, я відредагував цитований текст, додавши форматування коду…
Holger

33

IntelliJ має такий гарний плагін для цього випадку, як плагін налагодження Java Stream . Ви повинні перевірити це: https://plugins.jetbrains.com/plugin/9696-java-stream-debugger?platform=hootsuite

Він розширює вікно інструмента відладчика IDEA, додаючи кнопку Trace Current Stream Chain, яка стає активною, коли налагоджувач зупиняється всередині ланцюжка викликів API Stream.

Він має приємний інтерфейс для роботи з окремими потоковими операціями і дає можливість слідувати деяким значенням, які вам слід налагоджувати.

Відладчик потоку Java

Ви можете запустити його вручну з вікна налагодження, натиснувши тут:

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


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 звичайно.
Федеріко П'яцца

1
@Fede Схоже, інша відповідь уже оновлена, тому не потрібно оновлювати шахту. Я ненавиджу багаторядкові лямбдахи. :-)
Стюарт Маркс

1
@Stuart Marks: Я теж вважаю за краще однорядкові лямбдахи. Тому зазвичай я видаляю розриви рядків після налагодження "що застосовується і до інших (звичайних) складових операторів".
Холгер

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());
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.