Я пытаюсь преобразовать в лямбда-функцию
Пока я могу преобразовать приведенный выше код в лямбда-функцию, как показано ниже.
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);
}
}
});
Но все же моей целью было избежать повторения одной и той же логики, а также преобразовать в лямбда-функцию, но в моей преобразованной лямбда-функции я чувствую ее нечистой и эффективной.
Это просто для моего учебного аспекта. Я делаю это, беря один существующий фрагмент кода.
Может ли кто-нибудь сказать мне, как я могу импровизировать преобразованную лямбда-функцию
Если эта часть вашего приложения не чувствительна к производительности (например, действительно чувствительна), я бы сделал один stream.map() и т. д. для empIdAccepted
, один для dateAccepted
и т. д., поскольку он более понятен и разделяет отображаемую вами логику на разные аспекты. которые не связаны друг с другом. Это основа функционального программирования: разбиение на подоперации или шаги.
Моя цель - преобразовать в лямбда-функцию без повторения кода или логики.
Функциональное программирование частично связано с более гранулированным подходом, при котором вы фрагментируете/разделяете работу на несколько шагов — повторяющийся код и/или шаги даются, когда вы перестаете реализовывать функции, которые делают все сами по себе (например, проверка нуля + преобразование вместо одного предмет)
На вашем шаге filter
элементы потока кажутся массивами (вы обращаетесь к list.length
), затем на шаге forEach
они внезапно должны быть экземплярами EmployeeValidationAccepted
или EmployeeValidationRejected
. Это не работает. Также неконструктивно заменять ваш работающий исходный код этим неполным сломанным кодом. В исходном коде у вас были dateRejected
и empIdRejected
, но теперь у вас есть dates1
, dates2
, empIds1
и empIds2
, которые нигде не объявлены.
Как насчет этого:
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
вместо того, чтобы собирать оба варианта, а затем фильтровать, как вы описали.
Это предполагает, что идентификаторы действительно уникальны и что порядок не имеет значения. Кроме того, он выполняет split
дважды для каждого элемента.
Как правило, когда вы пытаетесь реорганизовать код, вы должны сосредоточиться только на необходимых изменениях.
Просто потому, что вы собираетесь использовать 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);
Это довольно большой пример, и приведенный ниже код проверяет instanceof, чего не делает императив. Какова ваша цель? Использование instanceof кажется мне плохим стилем