Преобразование итерации массива в лямбда-функцию с использованием Java8

Я пытаюсь преобразовать в лямбда-функцию

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

Stream.of(acceptedDetails, rejectedDetails)
.filter(list -> !isNull(list) && list.length > 0)
.forEach(new Consumer<Object>() {
    public void accept(Object acceptedOrRejected) {
        String id;
        if (acceptedOrRejected instanceof EmployeeValidationAccepted) {
            id = ((EmployeeValidationAccepted) acceptedOrRejected).getId();
        } else {
            id = ((EmployeeValidationRejected) acceptedOrRejected).getAd().getId();
        }

        if (acceptedOrRejected instanceof EmployeeValidationAccepted) {
            dates1.add(new Integer(id.split("something")[1]));
            Integer empId = Integer.valueOf(id.split("something")[2]);
            empIds1.add(empId);
        } else {
            dates2.add(new Integer(id.split("something")[1]));
            Integer empId = Integer.valueOf(id.split("something")[2]);
            empIds2.add(empId);
        }
    }
});

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

Это просто для моего учебного аспекта. Я делаю это, беря один существующий фрагмент кода.

Может ли кто-нибудь сказать мне, как я могу импровизировать преобразованную лямбда-функцию

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

roookeee 28.05.2019 00:25

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

roookeee 28.05.2019 00:27

Моя цель - преобразовать в лямбда-функцию без повторения кода или логики.

Alex Man 28.05.2019 00:30

Функциональное программирование частично связано с более гранулированным подходом, при котором вы фрагментируете/разделяете работу на несколько шагов — повторяющийся код и/или шаги даются, когда вы перестаете реализовывать функции, которые делают все сами по себе (например, проверка нуля + преобразование вместо одного предмет)

roookeee 28.05.2019 00:43

На вашем шаге filter элементы потока кажутся массивами (вы обращаетесь к list.length), затем на шаге forEach они внезапно должны быть экземплярами EmployeeValidationAccepted или EmployeeValidationRejected. Это не работает. Также неконструктивно заменять ваш работающий исходный код этим неполным сломанным кодом. В исходном коде у вас были dateRejected и empIdRejected, но теперь у вас есть dates1, dates2, empIds1 и empIds2, которые нигде не объявлены.

Holger 28.05.2019 10:57
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
3
5
113
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Как насчет этого:

 class EmployeeValidationResult {
    //constructor + getters omitted for brevity
    private final BigInteger date;
    private final Integer employeeId;
}

List<EmployeeValidationResult> accepted = Stream.of(acceptedDetails)
    .filter(Objects:nonNull)
    .map(this::extractValidationResult)
    .collect(Collectors.toList());

List<EmployeeValidationResult> rejected = Stream.of(rejectedDetails)
    .filter(Objects:nonNull)
    .map(this::extractValidationResult)
    .collect(Collectors.toList());


EmployeeValidationResult extractValidationResult(EmployeeValidationAccepted accepted) {
    return extractValidationResult(accepted.getId());
}

EmployeeValidationResult extractValidationResult(EmployeeValidationRejected rejected) {
    return extractValidationResult(rejected.getAd().getId());
}

EmployeeValidationResult extractValidationResult(String id) {
    String[] empIdList = id.split("-");
    BigInteger date = extractDate(empIdList[1])
    Integer empId = extractId(empIdList[2]);

    return new EmployeeValidationResult(date, employeeId);
}

Повторение операций filter или map — это хороший стиль и ясное представление о том, что происходит. Объединение двух списков объектов в один и использование instanceof загромождает реализацию и делает ее менее читабельной/поддерживаемой.

Ответ принят как подходящий

Аналогичный подход, который уже опубликован @roookeee, но, возможно, немного более краткий, заключается в сохранении сопоставлений с использованием функций сопоставления, объявленных как:

Function<String, Integer> extractEmployeeId = empId -> Integer.valueOf(empId.split("-")[2]);
Function<String, BigInteger> extractDate = empId -> new BigInteger(empId.split("-")[1]);

затем продолжите сопоставление как:

Map<Integer, BigInteger> acceptedDetailMapping = Arrays.stream(acceptedDetails)
        .collect(Collectors.toMap(a -> extractEmployeeId.apply(a.getId()),
                a -> extractDate.apply(a.getId())));

Map<Integer, BigInteger> rejectedDetailMapping = Arrays.stream(rejectedDetails)
        .collect(Collectors.toMap(a -> extractEmployeeId.apply(a.getAd().getId()),
                a -> extractDate.apply(a.getAd().getId())));

В дальнейшем вы также можете получить доступ к дате принятия или отклонения, соответствующей employeeId сотрудника.

Еще одна хорошая идея! Мой ответ можно было бы расширить с помощью Stream.concat вместо того, чтобы собирать оба варианта, а затем фильтровать, как вы описали.

roookeee 28.05.2019 11:29

Это предполагает, что идентификаторы действительно уникальны и что порядок не имеет значения. Кроме того, он выполняет split дважды для каждого элемента.

Holger 28.05.2019 11:53

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

Просто потому, что вы собираетесь использовать Stream API, нет причин загромождать код проверками на null или пустыми массивами, которых не было в коде на основе цикла. Вы также не должны менять BigInteger на Integer.

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

Во-первых, давайте посмотрим, как бы мы сделали это для традиционного цикла:

static void addToLists(String id, List<Integer> empIdList, List<BigInteger> dateList) {
    String[] array = id.split("-");
    dateList.add(new BigInteger(array[1]));
    empIdList.add(Integer.valueOf(array[2]));
}
List<Integer> empIdAccepted = new ArrayList<>();
List<BigInteger> dateAccepted = new ArrayList<>();

for(EmployeeValidationAccepted acceptedDetail : acceptedDetails) {
    addToLists(acceptedDetail.getId(), empIdAccepted, dateAccepted);
}

List<Integer> empIdRejected = new ArrayList<>();
List<BigInteger> dateRejected = new ArrayList<>();

for(EmployeeValidationRejected rejectedDetail : rejectedDetails) {
    addToLists(rejectedDetail.getAd().getId(), empIdRejected, dateRejected);
}

Если мы хотим выразить то же самое, что и потоковые операции, существует препятствие, заключающееся в наличии двух результатов для каждой операции. В JDK 12 действительно появилось встроенное решение:

static Collector<String,?,Map.Entry<List<Integer>,List<BigInteger>>> idAndDate() {
    return Collectors.mapping(s -> s.split("-"),
        Collectors.teeing(
            Collectors.mapping(a -> Integer.valueOf(a[2]), Collectors.toList()),
            Collectors.mapping(a -> new BigInteger(a[1]),  Collectors.toList()),
            Map::entry));
}
Map.Entry<List<Integer>, List<BigInteger>> e;
e = Arrays.stream(acceptedDetails)
        .map(EmployeeValidationAccepted::getId)
        .collect(idAndDate());

List<Integer> empIdAccepted = e.getKey();
List<BigInteger> dateAccepted = e.getValue();

e = Arrays.stream(rejectedDetails)
    .map(r -> r.getAd().getId())
    .collect(idAndDate());

List<Integer> empIdRejected = e.getKey();
List<BigInteger> dateRejected = e.getValue();

Поскольку метод не может вернуть два значения, для их хранения используется Map.Entry.

Чтобы использовать это решение с версиями Java до JDK 12, вы можете использовать реализацию, опубликованную в конце этот ответ. Тогда вам также придется заменить Map::entry на AbstractMap.SimpleImmutableEntry::new.

Или вы используете специальный сборщик, написанный для этой конкретной операции:

static Collector<String,?,Map.Entry<List<Integer>,List<BigInteger>>> idAndDate() {
    return Collector.of(
        () -> new AbstractMap.SimpleImmutableEntry<>(new ArrayList<>(), new ArrayList<>()),
        (e,id) -> {
            String[] array = id.split("-");
            e.getValue().add(new BigInteger(array[1]));
            e.getKey().add(Integer.valueOf(array[2]));
        },
        (e1, e2) -> {
            e1.getKey().addAll(e2.getKey());
            e1.getValue().addAll(e2.getValue());
            return e1;
        });
}

Другими словами, использование Stream API не всегда упрощает код.

И последнее замечание: нам не нужно использовать Stream API для использования лямбда-выражений. Мы также можем использовать их для перемещения цикла в общий код.

static <T> void addToLists(T[] elements, Function<T,String> tToId,
                           List<Integer> empIdList, List<BigInteger> dateList) {
    for(T t: elements) {
        String[] array = tToId.apply(t).split("-");
        dateList.add(new BigInteger(array[1]));
        empIdList.add(Integer.valueOf(array[2]));
    }
}
List<Integer> empIdAccepted = new ArrayList<>();
List<BigInteger> dateAccepted = new ArrayList<>();
addToLists(acceptedDetails, EmployeeValidationAccepted::getId, empIdAccepted, dateAccepted);

List<Integer> empIdRejected = new ArrayList<>();
List<BigInteger> dateRejected = new ArrayList<>();
addToLists(rejectedDetails, r -> r.getAd().getId(), empIdRejected, dateRejected);

Другие вопросы по теме