Wie behalte ich die Iterationsreihenfolge einer Liste bei, wenn Collections.toMap () in einem Stream verwendet wird?

83

Ich erstelle ein Mapaus einem Listwie folgt:

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

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

Ich möchte die gleiche Iterationsreihenfolge wie in der beibehalten List. Wie kann ich LinkedHashMapmit den Collectors.toMap()Methoden eine erstellen ?

Ashika Umanga Umagiliya
quelle
1
Bitte überprüfen Sie meine Antwort unten. Es ist nur 4 Zeilen Code eine benutzerdefinierte Verwendung Supplier, Accumulatorund Combinerdie collectMethode Ihrer stream:)
hzitoun
1
Die Frage wurde bereits beantwortet. Ich möchte nur den Weg zur Beantwortung dieser Frage erläutern. (1) Wenn Sie eine Reihenfolge in der Karte wünschen, müssen Sie LinkedHashMap verwenden. (2) Collectors.toMap () hat viele Implementierungen, eine davon das fragt nach einer Karte. Verwenden Sie also eine LinkedHashMap, in der Map erwartet wird.
Satyendra Kumar

Antworten:

118

Die 2-Parameter-Version vonCollectors.toMap() verwendet a 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);
}

Um die 4-Parameter-Version zu verwenden , können Sie Folgendes ersetzen:

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

mit:

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

Oder um es ein bisschen sauberer zu machen, schreiben Sie eine neue toLinkedMap()Methode und verwenden Sie diese:

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
        );
    }
}
prunge
quelle
2
Warum so komplex? Sie können das leicht tun, überprüfen Sie meine Antwort unten
hzitoun
1
mergeFunctiondass in der 4-Parameter-Version Collectors.toMap()keine Ahnung hat, welcher Schlüssel zusammengeführt wird, beide uund vsind die Werte. Die Nachricht über IllegalStateException ist also nicht so richtig.
MoonFruit
1
@hzitoun, denn wenn Sie einfach verwenden Map::put, erhalten Sie (möglicherweise) unterschiedliche Werte für denselben Schlüssel. Durch die Verwendung Map::puthaben Sie indirekt ausgewählt, dass der erste Wert, auf den Sie stoßen, falsch ist. Ist es das, was der Endbenutzer will? Bist du sicher? Wenn ja, dann sicher verwenden Map::put. Andernfalls sind Sie sich nicht sicher und können sich nicht entscheiden: Lassen Sie den Benutzer wissen, dass sein Stream zwei identischen Schlüsseln mit unterschiedlichen Werten zugeordnet ist. Ich hätte Ihre eigene Antwort kommentiert, aber sie ist derzeit gesperrt.
Olivier Grégoire
70

Bieten Sie Ihren eigenen Supplier, Accumulatorund 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
quelle
1
@Sushil Akzeptierte Antwort ermöglicht die Wiederverwendung von Logik
Zylinder.y
1
Der Stream stützt sich auf die Nebenwirkungen, map.put(..)die falsch sind.
Nikolas Charalambidis
Weißt du was schneller ist? Verwenden Sie Ihre Version oder die akzeptierte Antwort?
nimo23
@ Nikolas kannst du bitte erklären was du mit Nebenwirkungen meinst ? Ich habe tatsächlich das Problem zu entscheiden, welches ich wählen soll: stackoverflow.com/questions/61479650/…
nimo23
0

In Kotlin toMap()ist die Ordnung erhalten.

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

Gibt eine neue Karte zurück, die alle Schlüssel-Wert-Paare aus der angegebenen Sammlung von Paaren enthält.

Die zurückgegebene Karte behält die Reihenfolge der Eintragsiteration der ursprünglichen Sammlung bei. Wenn eines von zwei Paaren denselben Schlüssel hätte, wird das letzte zur Karte hinzugefügt.

Hier ist seine Implementierung:

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

Die Verwendung ist einfach:

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

Die zugrunde liegende Sammlung für mapist a LinkedHashMap(die nach Einfügung sortiert ist).

Mateen Ulhaq
quelle
0

Einfache Funktion zum Zuordnen von Objekten zu bestimmten Feldern:

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()
);
Dmitri Algazin
quelle