Рассмотрим этот код:
Function<BigDecimal,BigDecimal> func1 = x -> x;//This could be anything
Function<BigDecimal,BigDecimal> func2 = y -> y;//This could be anything
Map<Integer,BigDecimal> data = new HashMap<>();
Map<Integer,BigDecimal> newData =
data.entrySet().stream().
collect(Collectors.toMap(Entry::getKey,i ->
func1.apply(i.getValue())));
List<BigDecimal> list =
newData.entrySet().stream().map(i ->
func2.apply(i.getValue())).collect(Collectors.toList());
В основном я обновляю HashMap с помощью func1, чтобы применить вторую трансформацию с помощью func2 и сохранить значение, обновленное во второй раз, в списке. Я СДЕЛАЛ все неизменным способом, создавая новые объекты newData и list.
МОЙ ВОПРОС: Можно ли выполнить потоковую передачу исходной HashMap (данных) один раз?
Я пробовал это:
Function<BigDecimal,BigDecimal> func1 = x -> x;
Function<BigDecimal,BigDecimal> func2 = y -> y;
Map<Integer,BigDecimal> data = new HashMap<>();
List<BigDecimal> list = new ArrayList<>();
Map<Integer,BigDecimal> newData =
data.entrySet().stream().collect(Collectors.toMap(
Entry::getKey,i ->
{
BigDecimal newValue = func1.apply(i.getValue());
//SIDE EFFECT!!!!!!!
list.add(func2.apply(newValue));
return newValue;
}));
но при этом у меня есть побочный эффект при обновлении списка, поэтому я потерял требование «неизменяемости».




Ваш код можно упростить следующим образом:
List<BigDecimal> list = data.values().stream()
.map(func1)
.map(func2)
.collect(Collectors.toList());
Ваша цель - применить эти функции ко всем значениям BigDecimal в Map. Вы можете получить все эти значения из карты, используя Map::values, который возвращает List. Затем примените поток только к списку. Считайте, что data уже содержит несколько записей:
List<BigDecimal> list = data.values().stream()
.map(func1)
.map(func2)
.collect(Collectors.toList());
Я не рекомендую вам повторять все записи (Set<Entry<Integer, BigDecimal>>), поскольку вам нужно работать только со значениями.
Вместе со списком, который мне нужен на будущее, карта обновлялась только с func1.
То же, что этот ответ, а не то, что ищет OP. Проверьте этот ответ для более чистого решения.
@giuseppemaugeri: Не могли бы вы лучше описать проблему в вопросе? Не уверен, что правильно понял.
@nullpointer: Похоже, мы поняли вопрос точно так же.
Попробуйте таким образом, чтобы он вернул массив объекта [2], первый - это карта, а второй - список.
Map<Integer, BigDecimal> data = new HashMap<>();
data.put(1, BigDecimal.valueOf(30));
data.put(2, BigDecimal.valueOf(40));
data.put(3, BigDecimal.valueOf(50));
Function<BigDecimal, BigDecimal> func1 = x -> x.add(BigDecimal.valueOf(10));//This could be anything
Function<BigDecimal, BigDecimal> func2 = y -> y.add(BigDecimal.valueOf(-20));//This could be anything
Object[] o = data.entrySet().stream()
.map(AbstractMap.SimpleEntry::new)
.map(entry -> {
entry.setValue(func1.apply(entry.getValue()));
return entry;
})
.collect(Collectors.collectingAndThen(toMap(Map.Entry::getKey, Map.Entry::getValue), a -> {
List<BigDecimal> bigDecimals = a.values().stream().map(func2).collect(Collectors.toList());
return new Object[]{a,bigDecimals};
}));
System.out.println(data);
System.out.println((Map<Integer, BigDecimal>)o[0]);
System.out.println((List<BigDecimal>)o[1]);
Выход:
Original Map: {1=30, 2=40, 3=50}
func1 map: {1=40, 2=50, 3=60}
func1+func2 list: [20, 30, 40]
Это кажется идеальным вариантом использования метода Collectors.teeing в JDK 12. Вот webrev, а здесь CSR. Вы можете использовать его следующим образом:
Map.Entry<Map<Integer, BigDecimal>, List<BigDecimal>> result = data.entrySet().stream()
.collect(Collectors.teeing(
Collectors.toMap(
Map.Entry::getKey,
i -> func1.apply(i.getValue())),
Collectors.mapping(
i -> func1.andThen(func2).apply(i.getValue()),
Collectors.toList()),
Map::entry));
Collectors.teeing собирает данные в два разных коллектора, а затем объединяет оба частичных результата в окончательный результат. На этом последнем шаге я использую статический метод Map.entry(K k, V v) JDK 9, но я мог бы использовать любой другой контейнер, то есть Pair или Tuple2 и т. д.
Для первого сборщика я использую ваш точный код для сбора данных в Map, а для второго сборщика я использую Collectors.mapping вместе с Collectors.toList, используя Function.andThen для создания ваших функций func1 и func2 для этапа сопоставления.
Обновлено: Если вы не можете дождаться выпуска JDK 12, вы можете пока использовать этот код:
public static <T, A1, A2, R1, R2, R> Collector<T, ?, R> teeing(
Collector<? super T, A1, R1> downstream1,
Collector<? super T, A2, R2> downstream2,
BiFunction<? super R1, ? super R2, R> merger) {
class Acc {
A1 acc1 = downstream1.supplier().get();
A2 acc2 = downstream2.supplier().get();
void accumulate(T t) {
downstream1.accumulator().accept(acc1, t);
downstream2.accumulator().accept(acc2, t);
}
Acc combine(Acc other) {
acc1 = downstream1.combiner().apply(acc1, other.acc1);
acc2 = downstream2.combiner().apply(acc2, other.acc2);
return this;
}
R applyMerger() {
R1 r1 = downstream1.finisher().apply(acc1);
R2 r2 = downstream2.finisher().apply(acc2);
return merger.apply(r1, r2);
}
}
return Collector.of(Acc::new, Acc::accumulate, Acc::combine, Acc::applyMerger);
}
Примечание. Характеристики нижележащих коллекторов не учитываются при создании возвращенного коллектора (оставлено в качестве упражнения).
Обновлено еще раз: Ваше решение абсолютно нормально, даже если оно использует два потока. Мое решение, приведенное выше, передает исходную карту только один раз, но дважды применяет func1 ко всем значениям. Если func1 стоит дорого, вы можете рассмотреть его как запоминание (т.е. кэширование его результатов, чтобы при повторном вызове с тем же вводом вы возвращали результат из кеша, а не вычисляли его снова). Или вы также можете сначала применить func1 к значениям исходной карты, а затем собрать с помощью Collectors.teeing.
Запоминать легко. Просто объявите этот служебный метод:
public <T, R> Function<T, R> memoize(Function<T, R> f) {
Map<T, R> cache = new HashMap<>(); // or ConcurrentHashMap
return k -> cache.computeIfAbsent(k, f);
}
А затем используйте его следующим образом:
Function<BigDecimal, BigDecimal> func1 = memoize(x -> x); //This could be anything
Теперь вы можете использовать этот мемоизированный func1, и он будет работать точно так же, как и раньше, за исключением того, что он будет возвращать результаты из кеша, когда его метод apply вызывается с аргументом, который использовался ранее.
Другое решение - сначала применить func1, а затем собрать:
Map.Entry<Map<Integer, BigDecimal>, List<BigDecimal>> result = data.entrySet().stream()
.map(i -> Map.entry(i.getKey(), func1.apply(i.getValue())))
.collect(Collectors.teeing(
Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue),
Collectors.mapping(
i -> func2.apply(i.getValue()),
Collectors.toList()),
Map::entry));
Опять же, я использую статический метод jdk9 Map.entry(K k, V v).
Спасибо, что поделились обновлением. Просто не мог сравнить внесенную здесь сложность с другое решение. ... может вопрос там неправильно интерпретируется?
@nullpointer Дело в том, что OP хочет оба новую карту с только func1, примененным к значениям, а также список с func2 ∘ func1, примененным к ним.
Хм, теперь я могу выбрать функцию слияния и тип возврата вашего решения. Интересно.
Значит, вам нужно правильно получить два объекта из одного потока, один список и одну карту?