Просто создается мапа с явным указанием типа ключа и значений, в цикле проходишь по списку и добавляешь элементы в мапу при помощи 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);