Группировка элементов коллекции без Stream API
Есть метод, который группирует элементы коллекции из нескольким полям.
public void test() {
List<Item> itemsList = ...//Чтение из XML;
Map<List<Object>, List<Item>> groupBy =
itemsList.stream().collect(Collectors.groupingBy(
i -> Arrays.asList(i.id, i.city, i.order_status)));
for (Map.Entry<List<Object>, List<Item>> t : groupBy.entrySet()) {
System.out.println(t.getKey());
for (Item item : t.getValue()) {
System.out.println(" " + item);
}
}
}
А как сделать такое же без Stream API?
Ответы (1 шт):
Просто создается мапа с явным указанием типа ключа и значений, в цикле проходишь по списку и добавляешь элементы в мапу при помощи Map::computeIfAbsent -- этот метод при необходимости создаёт новое или возвращает существующее значение-список, для которого можно сразу же вызвать метод List:add.
При этом лучше использовать LinkedHashMap, чтобы порядок элементов в ней был стабильный (HashMap порядка не гарантирует).
public static Map<List<Object>, List<Item>> groupBy(List<Item> list) {
Map<List<Object>, List<Item>> map = new LinkedHashMap<>();
for (Item item : list) {
List<Object> key = Arrays.asList(item.getId(), item.getCity(), item.getOrderStatus());
map.computeIfAbsent(key, k -> new ArrayList<>()).add(item);
}
return map;
}
Следует отметить, что вместо списка объектов правильнее было бы использовать некий промежуточный класс, для которого нужно корректно переопределить методы hashCode и equals. Это достаточно просто сделать при помощи кортежей record, официально поддерживаемых с Java 16, или Lombok-аннотаций @Data / @AllArgsConstructor.
enum OrderStatus {NEW, PROCESSING, COMPLETED, CANCELED};
// определены конструктор, геттеры без префикса `get`, hashCode / equals, toString
record MyKey(long id, String city, OrderStatus status) {
public MyKey(Item item) {
this(item.getId(), item.getCity(), item.getOrderStatus());
}
}
public static Map<MyKey, List<Item>> groupBy(List<Item> list) {
Map<MyKey, List<Item>> map = new LinkedHashMap<>();
list.forEach(item -> map
.computeIfAbsent(new MyKey(item), k -> new ArrayList<>())
.add(item)
);
// отладочный вывод
map.forEach((key, val) -> {
System.out.println(key);
val.forEach(item -> System.out.println("\t" + item));
});
return map;
}
При желании создание такой группировки можно параметризовать для любого ключа, если передавать некую функцию-конвертор Function<Item, Key>, в последнем примере такой функцией был конструктор кортежа MyKey(Item item). Например, для генерации ключа-списка можно было бы определить отдельный фабричный метод:
// MyClass
public static List<Object> keyIdCityStatus(Item item) {
return Arrays.asList(item.getId(), item.getCity(), item.getOrderStatus());
}
Параметризованный метод выглядит так:
public static<K> Map<K, List<Item>> groupBy(Function<Item, K> keyBuilder, List<Item> list) {
Map<K, List<Item>> map = new LinkedHashMap<>();
list.forEach(item -> map
.computeIfAbsent(keyBuilder.apply(item), k -> new ArrayList<>())
.add(item)
);
return map;
}
Соответственно вызывать его можно для любого ключа:
// ссылка на фабричный метод MyClass::keyIdCityStatus
Map<List<Object>, List<Item>> keyListMap = groupBy(MyClass::keyIdCityStatus, list);
// группировка по двум полям с помощью лямбды
Map<List<Object>, List<Item>> byIdAndStatus = groupBy(
item -> List.of(item.getId(), item.getOrderStatus()),
list
);
// ссылка на конструктор MyKey::new
Map<MyKey, List<Item>> keyRecordMap = groupBy(MyKey::new, list);