У меня в коде повторяется следующий шаблон:
class X<T, V>
{
V doTransform(T t) {
return null; // dummy implementation
}
Map<T, V> transform(List<T> item) {
return item.stream().map(x->new AbstractMap.SimpleEntry<>(x, doTransform(x))).collect(toMap(x->x.getKey(), x->x.getValue()));
}
}
Требовать использования AbstractMap.SimpleEntry беспорядочно и неуклюже. Использование Linqs анонимных типов более элегантно.
Есть ли более простой способ добиться этого с помощью потоков?
Спасибо заранее.




Вы можете вызвать doTransform в преобразователе значений:
Map<T, V> transform(List<T> item) {
return item.stream().collect(toMap(x -> x, x -> doTransform(x)));
}
К сожалению, в Java нет точного эквивалента анонимных типов C#.
В этом конкретном случае вам не нужна промежуточная операция map, как предложил @Jorn Vernee. вместо этого вы можете выполнить извлечение ключей и значений в сборщике toMap.
Однако когда дело доходит до случаев, когда вы думаете, что вам нужно что-то подобное из анонимных типов C#, вы можете подумать:
Arrays.asList(...), List.of(...) (может не всегда быть тем, что вам нужно, в зависимости от вашего варианта использования)В конечном итоге, если вам нужно сопоставить В самом деле с чем-то, что может содержать два разных типа элементов, я бы остановился на AbstractMap.SimpleEntry.
Тем не менее, ваш текущий пример можно упростить до:
Map<T, V> transform(List<T> items) {
return items.stream().collect(toMap(Function.identity(),this::doTransform));
}
В этом конкретном примере нет необходимости делать промежуточное хранилище:
Map<T, V> transform(List<T> item) {
return item.stream().collect(toMap(x -> x, x -> doTransform(x)));
}
Но если вам это нужно, Java 9 предлагает более простой заводской метод,
Map<T, V> transform(List<T> item) {
return item.stream()
.map(x -> Map.entry(x, doTransform(x)))
.collect(toMap(x -> x.getKey(), x -> x.getValue()));
}
до тех пор, пока вам не придется иметь дело с null.
Здесь вы можете использовать анонимный внутренний класс,
Map<T, V> transform(List<T> item) {
return item.stream()
.map(x -> new Object(){ T t = x; V v = doTransform(x); })
.collect(toMap(x -> x.t, x -> x.v));
}
но он менее эффективен. Это внутренний класс, который фиксирует ссылку на окружающий this, также он захватывает x, поэтому у вас есть два поля, t и синтетическое поле для захвата x, для одного и того же.
Последнее можно обойти с помощью метода, например
Map<T, V> transform(List<T> item) {
return item.stream()
.map(x -> new Object(){ T getKey() { return x; } V v = doTransform(x); })
.collect(toMap(x -> x.getKey(), x -> x.v));
}
Но читабельности это не добавляет.
Единственные истинные анонимные типы - это типы, созданные для лямбда-выражений, которые можно использовать для хранения информации с помощью функций более высокого порядка:
Map<T, V> transform(List<T> item) {
return item.stream()
.map(x -> capture(x, doTransform(x)))
.collect(HashMap::new, (m,f) -> f.accept(m::put), HashMap::putAll);
}
public static <A,B> Consumer<BiConsumer<A,B>> capture(A a, B b) {
return f -> f.accept(a, b);
}
но вы скоро столкнетесь с ограничениями системы типов Java (она все еще не является функциональным языком программирования), если попробуете это с более сложными сценариями.
Приятно !, я, наверное, немного поленился с ответом;) Еще есть фабричный метод
Map.entry(), начиная с Java 9, который немного короче.