Мне представилась интересная проблема со стороны моего коллеги, и я не смог найти четкое и красивое решение Java 8. Проблема состоит в том, чтобы передать список POJO, а затем собрать их на карте, основанной на нескольких свойствах - сопоставление вызывает POJO несколько раз
Представьте следующее POJO:
private static class Customer {
public String first;
public String last;
public Customer(String first, String last) {
this.first = first;
this.last = last;
}
public String toString() {
return "Customer(" + first + " " + last + ")";
}
}
Задайте его как List<Customer>
:
// The list of customers
List<Customer> customers = Arrays.asList(
new Customer("Johnny", "Puma"),
new Customer("Super", "Mac"));
Альтернатива 1. Используйте Map
вне "потока" (или, скорее, вне forEach
).
// Alt 1: not pretty since the resulting map is "outside" of
// the stream. If parallel streams are used it must be
// ConcurrentHashMap
Map<String, Customer> res1 = new HashMap<>();
customers.stream().forEach(c -> {
res1.put(c.first, c);
res1.put(c.last, c);
});
Альтернатива 2. Создайте записи в карте и поместите их, а затем flatMap
их. IMO это немного слишком многословно и не так просто читать.
// Alt 2: A bit verbose and "new AbstractMap.SimpleEntry" feels as
// a "hard" dependency to AbstractMap
Map<String, Customer> res2 =
customers.stream()
.map(p -> {
Map.Entry<String, Customer> firstEntry = new AbstractMap.SimpleEntry<>(p.first, p);
Map.Entry<String, Customer> lastEntry = new AbstractMap.SimpleEntry<>(p.last, p);
return Stream.of(firstEntry, lastEntry);
})
.flatMap(Function.identity())
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue));
Альтернатива 3. Это еще одна, к которой я придумал "самый красивый" код, но он использует версию с тремя аргументами reduce
, а третий параметр немного изворотлив, найденный в этом вопросе: Назначение третьего аргумента функции "уменьшить" в функциональном программировании Java 8. Кроме того, reduce
не кажется подходящим для этой проблемы, так как он мутирует и параллельные потоки могут не работать с подходом ниже.
// Alt 3: using reduce. Not so pretty
Map<String, Customer> res3 = customers.stream().reduce(
new HashMap<>(),
(m, p) -> {
m.put(p.first, p);
m.put(p.last, p);
return m;
}, (m1, m2) -> m2 /* <- NOT USED UNLESS PARALLEL */);
Если вышеприведенный код напечатан следующим образом:
System.out.println(res1);
System.out.println(res2);
System.out.println(res3);
Результат:
{Супер = Клиент (Super Mac), Джонни = Клиент (Джонни Пума), Mac = Клиент (Супер Mac), Пума = Клиент (Джонни Пума)}
{Super = Клиент (Super Mac), Джонни = Клиент (Джонни Пума), Mac = Клиент (Супер Mac), Пума = Клиент (Джонни Пума)}
{Super = Клиент (Super Mac), Джонни = Клиент (Джонни Пума), Mac = Клиент (Супер Mac), Пума = Клиент (Джонни Пума)}
Итак, теперь на мой вопрос: как я должен, упорядоченным способом Java 8, передать поток через List<Customer>
, а затем как-то собрать его как Map<String, Customer>
, где вы разделили все на две клавиши (first
И last
), т.е. Customer
отображается дважды. Я не хочу использовать какие-либо сторонние библиотеки, я не хочу использовать карту вне потока, как в alt 1. Есть ли другие приятные альтернативы?
Полный код может быть найден на hastebin для простой копии-вставки, чтобы все это выполнялось.