Как вы группируете элементы в списке <p>на карту <K, List <V>> при сохранении порядка?
У меня есть список объектов Google PlaceSummary, взятых из API Google Адресов. Я хотел бы собрать и сгруппировать их по их идентификатору Google Place, но также сохранить порядок элементов. То, что я думал, будет работать:
Map<String, List<PlaceSummary>> placesGroupedByPlaceId =
places.stream()
.collect(Collectors.groupingBy(
PlaceSummary::getPlaceId,
LinkedHashMap::new,
Collectors.mapping(PlaceSummary::getPlaceId, toList())
));
Но он даже не будет компилироваться. Похоже, что в соответствии с документация Java API на коллекторах.
Раньше у меня был этот код:
Map<String, List<PlaceSummary>> placesGroupedByPlaceId = places.stream()
.collect(Collectors.groupingBy(PlaceSummary::getPlaceId));
Однако стандарт .collect()
в API потоков не сохраняет порядок элементов в последующем HashMap
(очевидно, поскольку HashMap
неупорядочен). Я хочу, чтобы результат был LinkedHashMap
, так что карта сортируется по порядку вставки каждого ведра.
Однако решение, которое я предложил, не компилируется. Во-первых, он не распознает PlaceSummary::getPlaceId
, поскольку он говорит, что это не функция, хотя я знаю, что это так. Во-вторых, он говорит, что я не могу преобразовать LinkedHashMap<Object, Object>
в M. M, как предполагается, является общей коллекцией, поэтому ее следует принять.
Как преобразовать List в LinkedHashMap
с помощью Java Stream API? Есть ли лаконичный способ сделать это? Если это слишком сложно понять, я могу просто прибегнуть к старым школьным пред-Java 8 методам.
Я заметил, что есть другой ответ при конвертировании списка в LinkedHashMap, но это не имеет решение, которое требуется, поскольку мне нужно собрать 'this' объект, который я специально перебираю.
Ответы
Ответ 1
Вы действительно близки к тому, что хотите:
Map<String, List<PlaceSummary>> placesGroupedByPlaceId =
places.stream()
.collect(Collectors.groupingBy(
PlaceSummary::getPlaceId,
LinkedHashMap::new,
Collectors.mapping(Function.identity(), Collectors.toList())
));
В методе Collectors.mapping
вам нужно указать экземпляр PlaceSummary
, а не идентификатор места. В приведенном выше коде я использовал Function.identity()
: этот сборщик используется для построения значений, поэтому нам нужно накапливать сами места (а не их идентификатор).
Обратите внимание, что можно писать непосредственно Collectors.toList()
вместо Collectors.mapping(Function.identity(), Collectors.toList())
.
Код, который вы до сих пор не компилируете, потому что он фактически создает Map<String, List<String>>
: вы накапливаете идентификаторы для каждого ID (что довольно странно).
Вы можете написать это как общий метод:
private static <K, V> Map<K, List<V>> groupByOrdered(List<V> list, Function<V, K> keyFunction) {
return list.stream()
.collect(Collectors.groupingBy(
keyFunction,
LinkedHashMap::new,
Collectors.toList()
));
}
и используйте его следующим образом:
Map<String, List<PlaceSummary>> placesGroupedById = groupByOrdered(places, PlaceSummary::getPlaceId);
Ответ 2
Я думаю, вы немного смутили окончательный коллекционер. Он просто представляет собой то, что должно быть в каждой стоимости карты. Нет необходимости иметь вторичный сборщик mapping
, поскольку вам просто нужен список исходных объектов.
Map<String, List<PlaceSummary>> placesGroupedByPlaceId =
places.stream()
.collect(Collectors.groupingBy(PlaceSummary::getPlaceId,
LinkedHashMap::new,
Collectors.toList()));