web-dev-qa-db-fra.com

Comment convertir un tableau en HashMap avec Java 8 Stream

J'écris une fonction pour convertir un tableau en carte en utilisant Java 8 Stream.

Voici ce que je voulais

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).????
}

Usages valables

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

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

Usages invalides

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

Des aides?

Merci!

8
Loc

Vous pouvez utiliser

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

mais cela vous donnera un avertissement (fondé) non vérifié. Votre méthode ne peut pas tenir la promesse de retourner un Map<K, V> correctement typé pour un tableau d’objets arbitraires et, pire encore, elle ne faillira pas avec une exception, mais renverra silencieusement une mappe incohérente si vous transmettez des objets d’un type incorrect.

Une solution plus propre et couramment utilisée est

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

Cela peut être compilé sans avertissement, car son exactitude sera vérifiée lors de l'exécution. Le code de l'appelant doit être adapté:

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

Outre la nécessité de spécifier les types réels en tant que littéraux de classe, il présente l'inconvénient de ne pas prendre en charge les types génériques de clé ou de valeur (car ils ne peuvent pas être exprimés sous la forme Class) et de ne pas disposer de sécurité au moment de la compilation, mais uniquement d'un contrôle à l'exécution.


Cela vaut la peine en regardant Java 9 . Là, vous pourrez faire:

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

Cela créera une carte immutable d'un type non spécifié, plutôt que d'une HashMap, mais le point intéressant est l'API.

Il existe une méthode <K,V> Map.Entry<K,V> entry(K k, V v) qui peut être combinée avec
<K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries) pour créer une carte de longueur variable (les variables sont toutefois limitées à 255 paramètres).

Vous pouvez implémenter une chose similaire:

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

Les méthodes de commodité of sont implémentées de la seule façon. Cela peut être fait avec le type safety: en tant que méthodes surchargées avec différents nombres d’arguments, comme

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 effectue la découpe à dix mappages, si vous en avez plus, vous devez utiliser la variante ofEntries(entry(k1, v1), …)).

Si vous suivez ce modèle, vous devriez conserver votre nom toMap ou utiliser uniquement map, plutôt que d'appeler à «of», car vous n'écrivez pas l'interface Map.

Ces surcharges peuvent ne pas sembler très élégantes, mais elles résolvent tous les problèmes. Vous pouvez écrire le code exactement comme dans votre question, sans spécifier d'objets Class, mais gagner la sécurité du type à la compilation et même le rejet des tentatives d'appel avec un nombre impair d'arguments.

Vous devez effectuer une réduction sur un certain nombre de paramètres, mais, comme nous l'avons déjà noté, même les variables ne supportent pas les paramètres illimités. Et le formulaire ofEntries(entry(…), …) n’est pas si mauvais pour les cartes plus grandes.


Le collecteur Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue) renvoie un type de carte non spécifié, qui peut même être immuable (bien que ce soit une HashMapdans la version actuelle). Si vous voulez avoir la garantie qu'une instance HashMap est renvoyée, vous devez utiliser Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1,v2)->{throw new IllegalArgumentException("duplicate key");}, HashMap::new) à la place.

8
Holger

Obtenir exactement ce que vous voulez ne fonctionnera probablement pas pour les cartes dont le type de clé diffère de leur type de valeur. En effet, la déclaration d'arity variable de Java (la partie Object... entries) ne prend en charge qu'un seul type.

Certaines options me viennent à l’esprit:

  1. Vous pouvez effectuer les vérifications de manière dynamique et générer une exception d’argument non conforme si les valeurs ne correspondent pas. Mais vous perdrez la vérification du type du compilateur.

  2. Vous pouvez définir une classe Pair et jouer un peu avec l'importation statique pour obtenir presque ce que vous voulez:

par exemple.: 

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
Michael Bar-Sinai

Voici mon idée par flux JDK 8:

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

Si cela ne vous dérange pas d'utiliser une bibliothèque tierce - AbacusUtil , le code pourrait être simplifié pour:

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

Et je pense que le moyen le plus efficace de le faire est de prendre la boucle, si vous ne poursuivez pas particulièrement à l'aide de Stream API

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

Vous pouvez utiliser quelque chose comme les littéraux de carte.
Pour ce faire, vous pouvez utiliser une méthode d'usine:

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

Enfin, vous pouvez faire quelque chose comme suit:

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

Sortie:

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

J'espère que cela vous donne le bon chemin.

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