wake-up-neo.com

Wie konvertiert man Array mit Java 8 Stream in HashMap?

Ich schreibe eine Funktion, um ein Array mit Java 8 Stream in Map zu konvertieren.

Hier ist was ich wollte

public static <K, V> Map<K, V> toMap(Object... entries) {
    // Requirements:
    // entries must be K1, V1, K2, V2, .... ( even length )
    if (entries.length % 2 == 1) {
        throw new IllegalArgumentException("Invalid entries");
    }

    // TODO
    Arrays.stream(entries).????
}

Gültige Verwendungen

Map<String, Integer> map1 = toMap("k1", 1, "k2", 2);

Map<String, String> map2 = toMap("k1", "v1", "k2", "v2", "k3", "v3");

Ungültige Verwendungen

Map<String, Integer> map1 = toMap("k1", 1, "k2", 2, "k3");

Irgendwelche hilft?

Vielen Dank!

8
Loc

Sie können verwenden

public static <K, V> Map<K, V> toMap(Object... entries) {
    if(entries.length % 2 == 1)
        throw new IllegalArgumentException("Invalid entries");
    return (Map<K, V>)IntStream.range(0, entries.length/2).map(i -> i*2)
        .collect(HashMap::new, (m,i)->m.put(entries[i], entries[i+1]), Map::putAll);
}

aber es gibt Ihnen eine (begründete) ungeprüfte Warnung. Ihre Methode kann das Versprechen nicht halten, für ein Array beliebiger Objekte einen korrekt getippten Map<K, V> - Wert zurückzugeben. Schlimmer noch, sie schlägt mit einer Ausnahme nicht fehl, sondern gibt im Hintergrund eine inkonsistente Karte zurück, wenn Sie Objekte von übergeben der falsche Typ.

Eine sauberere, häufig verwendete Lösung ist

public static <K, V> Map<K, V> toMap(
                               Class<K> keyType, Class<V> valueType, Object... entries) {
    if(entries.length % 2 == 1)
        throw new IllegalArgumentException("Invalid entries");
    return IntStream.range(0, entries.length/2).map(i -> i*2)
        .collect(HashMap::new,
                 (m,i)->m.put(keyType.cast(entries[i]), valueType.cast(entries[i+1])),
                 Map::putAll);
}

Dies kann ohne Warnung kompiliert werden, da die Korrektheit zur Laufzeit überprüft wird. Der Calling Code muss angepasst werden:

Map<String, Integer> map1 = toMap(String.class, Integer.class, "k1", 1, "k2", 2);
Map<String, String> map2 = toMap(
                           String.class, String.class, "k1", "v1", "k2", "v2", "k3", "v3");

Abgesehen davon, dass die tatsächlichen Typen als Klassenliterale angegeben werden müssen, hat dies den Nachteil, dass generische Schlüssel- oder Werttypen (da sie nicht als Class ausgedrückt werden können) nicht unterstützt werden und nur noch keine Sicherheit beim Kompilieren bieten eine Laufzeitprüfung.


Es lohnt sich mit Blick auf Java 9 . Dort können Sie:

Map<String, Integer> map1 = Map.of("k1", 1, "k2", 2);
Map<String, String>  map2 = Map.of("k1", "v1", "k2", "v2", "k3", "v3");

Dadurch wird eine unveränderliche Karte eines nicht angegebenen Typs anstelle einer HashMap erstellt, aber der interessante Punkt ist die API.

Es gibt eine Methode <K,V> Map.Entry<K,V> entry(K k, V v), mit der kombiniert werden kann
<K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries) zum Erstellen einer Map mit variabler Länge (Varargs sind jedoch immer noch auf 255 Parameter beschränkt).

Sie können eine ähnliche Sache implementieren:

public static <K,V> Map.Entry<K,V> entry(K k, V v) {
    return new AbstractMap.SimpleImmutableEntry<>(k, v);
}
public static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries) {
    return Arrays.stream(entries)
        .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

Die Convenience-Methode (n) of sind die einzige Möglichkeit, dies mit Typensicherheit zu tun: als überladene Methoden mit unterschiedlich vielen Argumenten, wie z

public static <K,V> Map<K,V> of() {
    return new HashMap<>();// or Collections.emptyMap() to create immutable maps
}
static <K,V> Map<K,V> of(K k1, V v1) {
    return ofEntries(entry(k1, v1));
}
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2) {
    return ofEntries(entry(k1, v1), entry(k2, v2));
}
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3) {
    return ofEntries(entry(k1, v1), entry(k2, v2), entry(k3, v3));
}
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
    return ofEntries(entry(k1, v1), entry(k2, v2), entry(k3, v3), entry(k4, v4));
}   
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5) {
    return ofEntries(entry(k1, v1), entry(k2, v2), entry(k3, v3), entry(k4, v4));
}   

(Java 9 schneidet mit zehn Zuordnungen, wenn Sie mehr haben, müssen Sie die Variante ofEntries(entry(k1, v1), …) verwenden.).

Wenn Sie diesem Muster folgen, sollten Sie Ihren toMap -Namen beibehalten oder nur map verwenden, anstatt "of" aufzurufen, da Sie das Map nicht schreiben ] _ Schnittstelle.

Diese Überladungen sehen vielleicht nicht sehr elegant aus, aber sie lösen alle Probleme. Sie können den Code genau wie in Ihrer Frage schreiben, ohne Class -Objekte anzugeben, aber die Sicherheit beim Kompilieren erhöhen und Versuche, ihn mit einer ungeraden Anzahl von Argumenten aufzurufen, sogar ablehnen.

Sie müssen eine bestimmte Anzahl von Parametern abschneiden, aber, wie bereits erwähnt, unterstützen selbst Varagramme nicht unbegrenzte Parameter. Und das ofEntries(entry(…), …) -Formular ist für größere Karten nicht so schlecht.


Der Collector Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue) gibt einen nicht angegebenen Kartentyp zurück, der möglicherweise sogar unveränderlich ist (obwohl er in der aktuellen Version ein HashMap ist). Wenn Sie die Garantie haben möchten, dass eine HashMap -Instanz zurückgegeben wird, müssen Sie stattdessen Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1,v2)->{throw new IllegalArgumentException("duplicate key");}, HashMap::new) verwenden.

10
Holger

Genau das zu bekommen, was Sie möchten, wird wahrscheinlich nicht für Karten funktionieren, deren Schlüsseltyp von ihrem Werttyp abweicht. Dies liegt daran, dass die Variablendaritätsdeklaration von Java (der Object... entries-Teil) nur einen Typ unterstützt.

Einige Optionen fallen mir ein:

  1. Sie könnten die Prüfungen dynamisch durchführen und eine ungültige Ausnahmebedingung auslösen, wenn die Werte nicht übereinstimmen. Sie verlieren jedoch die Compiler-Typüberprüfung.

  2. Sie könnten eine Pair-Klasse definieren und ein wenig mit dem statischen Import spielen, um fast das zu bekommen, was Sie möchten:

z.B.: 

class Pair<K,V> {
    final K k;
    final V v;
    Pair( K ak, V av) {
        k=ak;
        v=av;
    }
    static <A,B> Pair<A,B> p(A a, B b) {
        return new Pair(a,b);
    }
}

public class JavaTest8 {

    <K,V> Map<K,V> toMap( Pair<K,V>... pairs ) {
        return Arrays.stream(pairs).collect(Collectors.toMap(p->p.k, p->p.v));
    }

    public static void main(String[] args) {
        // Usage
        Map<String,Integer> sti = toMap( p("A",1), p("B",2) );
        Map<Integer,Boolean> itb = toMap( p(1,true), p(42,false) );
    }
}
2

Hier ist meine Idee von JDK 8 Stream:

public static <K, V> Map<K, V> toMap(final Object... entries) {
    // Requirements:
    // entries must be K1, V1, K2, V2, .... ( even length )
    if (entries.length % 2 == 1) {
        throw new IllegalArgumentException("Invalid entries");
    }

    final Map<K, V> map = new HashMap<>((int) (entries.length / 2 * 1.25 + 1));
    IntStream.range(0, entries.length / 2).forEach(i -> map.put((K) entries[i * 2], (V) entries[i * 2 + 1]));
    return map;

    // OR: 
    //    return IntStream.range(0, entries.length / 2).boxed().reduce(new HashMap<K, V>(), (m, i) -> {
    //        m.put((K) entries[i * 2], (V) entries[i * 2 + 1]);
    //        return m;
    //    }, (a, b) -> {
    //        a.putAll(b);
    //        return b;
    //    });
}

Wenn Sie die Drittanbieter-Bibliothek AbacusUtil nicht mögen, kann der Code vereinfacht werden:

public static <K, V> Map<K, V> toMap2(final Object... entries) {
    // Requirements:
    // entries must be K1, V1, K2, V2, .... ( even length )
    if (entries.length % 2 == 1) {
        throw new IllegalArgumentException("Invalid entries");
    }

    return Stream.of(entries).split0(2).toMap(e -> (K) e.get(0), e -> (V) e.get(1));
}

Ich denke, der effizienteste Weg, dies zu tun, ist die for-Schleife, wenn Sie die Stream-API nicht besonders verwenden

public static <K, V> Map<K, V> toMap3(final Object... entries) {
    // Requirements:
    // entries must be K1, V1, K2, V2, .... ( even length )
    if (entries.length % 2 == 1) {
        throw new IllegalArgumentException("Invalid entries");
    }

    final Map<K, V> map = new HashMap<>((int) (entries.length / 2 * 1.25 + 1));

    for (int i = 0, len = entries.length; i < len; i++) {
        map.put((K) entries[i], (V) entries[++i]);
    }

    return map;

    // OR just call the method in AbacusUtil.       
    // return N.asMap(entries);
}
1
user_3380739

Sie können so etwas wie Kartenliterale verwenden.
Um dies zu erreichen, können Sie eine Factory-Methode verwenden:

// Creates a map from a list of entries
@SafeVarargs
public static <K, V> Map<K, V> mapOf(Map.Entry<K, V>... entries) {
    LinkedHashMap<K, V> map = new LinkedHashMap<>();
    for (Map.Entry<K, V> entry : entries) {
        map.put(entry.getKey(), entry.getValue());
    }
    return map;
}

// Creates a map entry
public static <K, V> Map.Entry<K, V> entry(K key, V value) {
    return new AbstractMap.SimpleEntry<>(key, value);
}

Zum Schluss kannst du folgendes tun:

public static void main(String[] args) {
    Map<String, Integer> map = mapOf(entry("a", 1), entry("b", 2), entry("c", 3));
    System.out.println(map);
}

Ausgabe:

{a = 1, b = 2, c = 3}

Ich hoffe, das gibt Ihnen den richtigen Weg.

0
nazar_art
public static <K, V, E> Map<K, V> toMap(Function<E, K> toKey, Function<E, V> toValue, E[][] e){

        final Map<K, V> newMap = new HashMap<>();

        Arrays
                .stream(e, 0, e.length - 1)
                .forEach(s ->
                {
                    if (s[0] != null || s[1] != null)
                        newMap.put(toKey.apply(s[0]), toValue.apply(s[1]));
                }
                );

        return newMap;

}




public static void main(String[] args) {

        Object[][] objects = new Object[10][2];
        objects[0][0] ="Ahmet";
        objects[0][1] =28;
        objects[1][0] ="Mehmet";
        objects[1][1] =18;
        objects[2][0] ="Kemal";
        objects[2][1] =55;

Map<String, Integer> newMap = toMap((Object::toString), (Object v) -> Integer.parseInt(v.toString()), objects);

System.out.println(newMap.get("Ahmet") + " " + newMap.get("Kemal"));


}
0
Ahmet Emrebas