Как сохранить порядок итерации списка при использовании Collections.toMap () в потоке?

84

Я создаю Mapиз a Listследующим образом:

List<String> strings = Arrays.asList("a", "bb", "ccc");

Map<String, Integer> map = strings.stream()
    .collect(Collectors.toMap(Function.identity(), String::length));

Я хочу сохранить тот же порядок итераций, что и в List. Как я могу создать с LinkedHashMapпомощью Collectors.toMap()методов?

Ашика Уманга Умагилия
источник
1
пожалуйста, проверьте мой ответ ниже. Это всего 4 строчки кода с использованием SupplierAccumulatorCombinercollectstream
кастома
1
На вопрос уже дан ответ, я просто хочу указать путь к поиску ответа на этот вопрос. (1) Вы хотите порядок на карте, вы должны использовать LinkedHashMap (2) Collectors.toMap () имеет много реализаций, одна из который запрашивает карту. Поэтому используйте LinkedHashMap там, где он ожидает Map.
Satyendra Kumar

Ответы:

121

Версия 2-параметрCollectors.toMap() использует HashMap:

public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(
    Function<? super T, ? extends K> keyMapper, 
    Function<? super T, ? extends U> valueMapper) 
{
    return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}

Чтобы использовать 4-параметрическую версию , вы можете заменить:

Collectors.toMap(Function.identity(), String::length)

с участием:

Collectors.toMap(
    Function.identity(), 
    String::length, 
    (u, v) -> {
        throw new IllegalStateException(String.format("Duplicate key %s", u));
    }, 
    LinkedHashMap::new
)

Или, чтобы сделать его немного чище, напишите новый toLinkedMap()метод и используйте его:

public class MoreCollectors
{
    public static <T, K, U> Collector<T, ?, Map<K,U>> toLinkedMap(
        Function<? super T, ? extends K> keyMapper,
        Function<? super T, ? extends U> valueMapper)
    {
        return Collectors.toMap(
            keyMapper,
            valueMapper, 
            (u, v) -> {
                throw new IllegalStateException(String.format("Duplicate key %s", u));
            },
            LinkedHashMap::new
        );
    }
}
грандж
источник
2
Почему такая сложность? вы можете легко это сделать, посмотрите мой ответ ниже
hzitoun
1
mergeFunctionчто в версии Collectors.toMap()с 4 параметрами не имеет понятия, какой ключ объединяется, оба uи vявляются значениями. Таким образом, сообщение об исключении IllegalStateException неверно.
MoonFruit
1
@hzitoun, потому что, если вы просто используете Map::put, вы получите (возможно) разные значения для одного и того же ключа. Используя Map::put, вы косвенно указали, что первое встреченное вами значение неверно. Это то, чего хочет конечный пользователь? Вы уверены? Если да, то обязательно используйте Map::put. В противном случае вы не уверены и не можете решить: сообщите пользователю, что его поток сопоставлен с двумя одинаковыми ключами с разными значениями. Я бы прокомментировал ваш собственный ответ, но в настоящее время он заблокирован.
Оливье Грегуар
70

Предоставьте свои собственные Supplier, Accumulatorа также Combiner:

    List<String> myList = Arrays.asList("a", "bb", "ccc"); 
    // or since java 9 List.of("a", "bb", "ccc");
    
    LinkedHashMap<String, Integer> mapInOrder = myList
        .stream()
        .collect(
            LinkedHashMap::new,                                   // Supplier
            (map, item) -> map.put(item, item.length()),          // Accumulator
            Map::putAll);                                         // Combiner

    System.out.println(mapInOrder);  // {a=1, bb=2, ccc=3}
Hzitoun
источник
1
@Sushil Принято решение позволяет повторно использовать логику
cylinder.y
1
Поток полагается на побочные эффекты, например, map.put(..)что неверно.
Николас Хараламбидис
Вы знаете, что быстрее? Используете свою версию или принятый ответ?
nimo23
@Nikolas, не могли бы вы объяснить, что вы имеете в виду под побочными эффектами ? Мне действительно
сложно
0

В Котлине toMap()сохраняется порядок.

fun <K, V> Iterable<Pair<K, V>>.toMap(): Map<K, V>

Возвращает новую карту, содержащую все пары ключ-значение из заданного набора пар.

Возвращенная карта сохраняет порядок итерации входа исходной коллекции. Если у любой из двух пар будет один и тот же ключ, на карту добавляется последняя.

Вот его реализация:

public fun <K, V> Iterable<Pair<K, V>>.toMap(): Map<K, V> {
    if (this is Collection) {
        return when (size) {
            0 -> emptyMap()
            1 -> mapOf(if (this is List) this[0] else iterator().next())
            else -> toMap(LinkedHashMap<K, V>(mapCapacity(size)))
        }
    }
    return toMap(LinkedHashMap<K, V>()).optimizeReadOnlyMap()
}

Использование просто:

val strings = listOf("a", "bb", "ccc")
val map = strings.map { it to it.length }.toMap()

Базовая коллекция для map- это LinkedHashMap(которая упорядочена вставкой).

Матин Улхак
источник
0

Простая функция для отображения массива объектов по некоторому полю:

public static <T, E> Map<E, T> toLinkedHashMap(List<T> list, Function<T, E> someFunction) {
    return list.stream()
               .collect(Collectors.toMap(
                   someFunction, 
                   myObject -> myObject, 
                   (key1, key2) -> key1, 
                   LinkedHashMap::new)
               );
}


Map<String, MyObject> myObjectsByIdMap1 = toLinkedHashMap(
                listOfMyObjects, 
                MyObject::getSomeStringField()
);

Map<Integer, MyObject> myObjectsByIdMap2 = toLinkedHashMap(
                listOfMyObjects, 
                MyObject::getSomeIntegerField()
);
Дмитрий Алгазин
источник