在流上使用 Collections.toMap() 时如何保持列表的迭代顺序?

How do I keep the iteration order of a List when using Collections.toMap() on a stream?

我正在从 List 创建一个 Map,如下所示:

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

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

我想保持与 List 中相同的迭代顺序。如何使用 Collectors.toMap() 方法创建 LinkedHashMap

2-parameter version of 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-parameter version,您可以替换:

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
        );
    }
}

制作您自己的 SupplierAccumulatorCombiner

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);  // prints {a=1, bb=2, ccc=3}

在 Kotlin 中,toMap() 是保序的。

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

Returns a new map containing all key-value pairs from the given collection of pairs.

The returned map preserves the entry iteration order of the original collection. If any of two pairs would have the same key the last one gets added to the map.

这是它的实现:

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(按插入顺序排列)。

通过某个字段映射对象数组的简单函数:

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()
);

Java 9 开始,您可以收集 map entries 的列表,其顺序与原名单:

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

List<Map.Entry<String, Integer>> entries = strings.stream()
        .map(e -> Map.entry(e, e.length()))
        .collect(Collectors.toList());

System.out.println(entries); // [a=1, bb=2, ccc=3]

或者您可以用相同的方式通过单个条目收集 maps 列表:

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

List<Map<String, Integer>> maps = strings.stream()
        .map(e -> Map.of(e, e.length()))
        .collect(Collectors.toList());

System.out.println(maps); // [{a=1}, {bb=2}, {ccc=3}]

这个问题的正确解决方案是

当前---->2参数版本

Map<Integer, String> mapping = list.stream().collect(Collectors.toMap(Entity::getId, Entity::getName));

对 ----> 使用 4 参数版本的 Collectors.toMap 告诉供应商提供新的 LinkedHashMap:

Map<Integer, String> mapping = list.stream().collect(Collectors.toMap(Entity::getId, Entity::getName, (u, v) -> u, LinkedHashMap::new));

这会有所帮助。