Java 8 Liste <V> dans la carte <K, V>

934

Je veux traduire une liste d'objets en une carte en utilisant les flux et les lambdas de Java 8.

C'est ainsi que je l'écrirais en Java 7 et en dessous.

private Map<String, Choice> nameMap(List<Choice> choices) {
        final Map<String, Choice> hashMap = new HashMap<>();
        for (final Choice choice : choices) {
            hashMap.put(choice.getName(), choice);
        }
        return hashMap;
}

Je peux accomplir cela facilement en utilisant Java 8 et Guava mais je voudrais savoir comment faire cela sans Guava.

À la goyave:

private Map<String, Choice> nameMap(List<Choice> choices) {
    return Maps.uniqueIndex(choices, new Function<Choice, String>() {

        @Override
        public String apply(final Choice input) {
            return input.getName();
        }
    });
}

Et la goyave avec des lambdas Java 8.

private Map<String, Choice> nameMap(List<Choice> choices) {
    return Maps.uniqueIndex(choices, Choice::getName);
}
Tom Cammann
la source

Réponses:

1395

Sur la base de la Collectorsdocumentation, c'est aussi simple que:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName,
                                              Function.identity()));
zapl
la source
167
En passant, même après Java 8, le JDK ne peut toujours pas participer à un concours de concision. L'alternative Goyave ressemble tellement facile à lire: Maps.uniqueIndex(choices, Choice::getName).
Bogdan Calmac
3
En utilisant (importé statiquement) Seqde la bibliothèque JOOL (que je recommanderais à tous ceux qui utilisent Java 8), vous pouvez également améliorer la brièveté avec: seq(choices).toMap(Choice::getName)
lukens
5
Y a-t-il des avantages à utiliser Function.identity? Je veux dire, il -> il est plus court
shabunc
9
@shabunc Je ne connais aucun avantage et je m'utilise réellement it -> it. Function.identity()est utilisé ici principalement parce qu'il est utilisé dans la documentation référencée et c'est tout ce que je savais sur les lambdas au moment de la rédaction
zapl
12
@zapl, oh, en fait, il s'avère qu'il y a des raisons à cela - stackoverflow.com/questions/28032827/…
shabunc
307

Si votre clé n'est PAS garantie d'être unique pour tous les éléments de la liste, vous devez la convertir en un Map<String, List<Choice>>au lieu d'unMap<String, Choice>

Map<String, List<Choice>> result =
 choices.stream().collect(Collectors.groupingBy(Choice::getName));
Ulises
la source
76
Cela vous donne en fait Map <String, List <Choice>> qui traite de la possibilité de clés non uniques, mais ce n'est pas ce que l'OP a demandé. Dans Guava, Multimaps.index (choix, Choice :: getName) est probablement une meilleure option si c'est ce que vous voulez de toute façon.
Richard Nichols
ou utilisez plutôt Multimap <String, Choice> de Guava qui est très pratique dans les scénarios où la même clé correspond à plusieurs valeurs. Il existe différentes méthodes utilitaires disponibles dans Guava pour utiliser de telles structures de données plutôt que de créer une carte <chaîne, liste <choix>>
Neeraj B.
1
@RichardNichols pourquoi la Multimapsméthode de la goyave est-elle une meilleure option? Cela peut être un inconvénient car il ne retourne pas d' Mapobjet.
theyuv
156

Utilisez getName()comme clé et Choiceelle - même comme valeur de la carte:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName, c -> c));
Ole
la source
19
Veuillez écrire une description pour que l'utilisateur puisse comprendre.
Mayank Jain
4
C'est vraiment dommage qu'il n'y ait pas plus de détails ici, car je préfère cette réponse.
MadConan
Collectors.toMap(Choice::getName,c->c)(2 caractères plus courts)
Ferrybig
7
Il est égal à la choices.stream().collect(Collectors.toMap(choice -> choice.getName(),choice -> choice)); fonction première pour la clé, la deuxième fonction de la valeur
waterscar
16
Je sais combien il est facile de voir et de comprendre, c -> cmais Function.identity()porte plus d'informations sémantiques. J'utilise généralement une importation statique pour que je puisse simplement l'utiliseridentity()
Hank D
28

En voici une autre au cas où vous ne voudriez pas utiliser Collectors.toMap ()

Map<String, Choice> result =
   choices.stream().collect(HashMap<String, Choice>::new, 
                           (m, c) -> m.put(c.getName(), c),
                           (m, u) -> {});
Emre Colak
la source
1
Lequel est préférable d'utiliser alors Collectors.toMap()ou notre propre HashMap comme vous l'avez montré dans l'exemple ci-dessus?
Swapnil Gangrade
1
Cet exemple a fourni un exemple sur la façon de placer autre chose sur la carte. Je voulais une valeur non fournie par un appel de méthode. Merci!
th3morg
1
La troisième fonction d'argument n'est pas correcte. Là, vous devez fournir une fonction pour fusionner deux Hashmaps, quelque chose comme Hashmap :: putAll
jesantana
25

La plupart des réponses répertoriées manquent un cas lorsque la liste contient des éléments en double . Dans ce cas, la réponse sera lancée IllegalStateException. Reportez-vous au code ci-dessous pour gérer également les doublons de liste :

public Map<String, Choice> convertListToMap(List<Choice> choices) {
    return choices.stream()
        .collect(Collectors.toMap(Choice::getName, choice -> choice,
            (oldValue, newValue) -> newValue));
  }
Sahil Chhabra
la source
19

Une option de plus de manière simple

Map<String,Choice> map = new HashMap<>();
choices.forEach(e->map.put(e.getName(),e));
Renukeswar
la source
3
Il n'y a pas de différence viable dans l'utilisation de ce type ou du type java 7.
Ashish Lohia
3
J'ai trouvé cela plus facile à lire et à comprendre ce qui se passait que les autres mécanismes
Freiheit
2
Le SO a demandé avec Java 8 Streams.
Mehraj Malik
18

Par exemple, si vous souhaitez convertir des champs d'objet en carte:

Exemple d'objet:

class Item{
        private String code;
        private String name;

        public Item(String code, String name) {
            this.code = code;
            this.name = name;
        }

        //getters and setters
    }

Et l'opération convertit la liste en carte:

List<Item> list = new ArrayList<>();
list.add(new Item("code1", "name1"));
list.add(new Item("code2", "name2"));

Map<String,String> map = list.stream()
     .collect(Collectors.toMap(Item::getCode, Item::getName));
Piotr R
la source
12

Si cela ne vous dérange pas d'utiliser des bibliothèques tierces, la bibliothèque cyclops-react d'AOL (divulgation, je suis un contributeur) a des extensions pour tous les types de collections JDK , y compris List et Map .

ListX<Choices> choices;
Map<String, Choice> map = choices.toMap(c-> c.getName(),c->c);
John McClean
la source
10

J'essayais de le faire et j'ai constaté que, en utilisant les réponses ci-dessus, lors de l'utilisation Functions.identity()de la clé de la carte, j'avais des problèmes avec l'utilisation d'une méthode locale comme this::localMethodNametravailler réellement en raison de problèmes de frappe.

Functions.identity()fait en fait quelque chose à la frappe dans ce cas, de sorte que la méthode ne fonctionnerait qu'en retournant Objectet en acceptant un paramètre deObject

Pour résoudre ce problème, j'ai fini par abandonner Functions.identity()et utiliser à la s->splace.

Donc, mon code, dans mon cas, pour répertorier tous les répertoires à l'intérieur d'un répertoire, et pour chacun utilise le nom du répertoire comme clé de la carte, puis appelle une méthode avec le nom du répertoire et renvoie une collection d'éléments, ressemble à:

Map<String, Collection<ItemType>> items = Arrays.stream(itemFilesDir.listFiles(File::isDirectory))
.map(File::getName)
.collect(Collectors.toMap(s->s, this::retrieveBrandItems));
iZian
la source
10

Vous pouvez créer un flux d'index à l'aide d'un IntStream puis les convertir en carte:

Map<Integer,Item> map = 
IntStream.range(0,items.size())
         .boxed()
         .collect(Collectors.toMap (i -> i, i -> items.get(i)));
Vikas Suryawanshi
la source
1
Ce n'est pas une bonne option car vous effectuez un appel get () pour chaque élément, augmentant ainsi la complexité de l'opération (o (n * k) si items est une table de hachage).
Nicolas Nobelis
N'est-ce pas (i) sur une table de hachage O (1)?
Ivo van der Veeken du
@IvovanderVeeken le get (i) dans l'extrait de code est sur la liste, pas sur la carte.
Zaki
@Zaki Je parlais de la remarque de Nicolas. Je ne vois pas la complexité n * k si les éléments sont une table de hachage au lieu d'une liste.
Ivo van der Veeken,
8

J'écrirai comment convertir une liste en carte en utilisant des génériques et une inversion de contrôle . Méthode juste universelle!

Peut-être avons - nous une liste d'entiers ou une liste d' objets. La question est donc la suivante: quelle devrait être la clé de la carte?

créer une interface

public interface KeyFinder<K, E> {
    K getKey(E e);
}

utilisant maintenant l'inversion de contrôle:

  static <K, E> Map<K, E> listToMap(List<E> list, KeyFinder<K, E> finder) {
        return  list.stream().collect(Collectors.toMap(e -> finder.getKey(e) , e -> e));
    }

Par exemple, si nous avons des objets de livre, cette classe consiste à choisir la clé de la carte

public class BookKeyFinder implements KeyFinder<Long, Book> {
    @Override
    public Long getKey(Book e) {
        return e.getPrice()
    }
}
grep
la source
6

J'utilise cette syntaxe

Map<Integer, List<Choice>> choiceMap = 
choices.stream().collect(Collectors.groupingBy(choice -> choice.getName()));
user2069723
la source
11
groupingBycrée un Map<K,List<V>>, pas un Map<K,V>.
Christoffer Hammarström
3
Dup d'ulises répond. Et, String getName();(pas Integer)
Barett
5
Map<String, Set<String>> collect = Arrays.asList(Locale.getAvailableLocales()).stream().collect(Collectors
                .toMap(l -> l.getDisplayCountry(), l -> Collections.singleton(l.getDisplayLanguage())));
Kumar Abhishek
la source
4

Cela peut se faire de 2 manières. Que personne soit la classe que nous allons utiliser pour le démontrer.

public class Person {

    private String name;
    private int age;

    public String getAge() {
        return age;
    }
}

Soit les personnes la liste des personnes à convertir sur la carte

1.Utilisation de foreach simple et d'une expression lambda sur la liste

Map<Integer,List<Person>> mapPersons = new HashMap<>();
persons.forEach(p->mapPersons.put(p.getAge(),p));

2.Utilisez les collecteurs sur le flux défini dans la liste donnée.

 Map<Integer,List<Person>> mapPersons = 
           persons.stream().collect(Collectors.groupingBy(Person::getAge));
raja emani
la source
4

Il est possible d'utiliser des flux pour ce faire. Pour supprimer le besoin d'utiliser explicitement Collectors, il est possible d'importer de toMapmanière statique (comme recommandé par Effective Java, troisième édition).

import static java.util.stream.Collectors.toMap;

private static Map<String, Choice> nameMap(List<Choice> choices) {
    return choices.stream().collect(toMap(Choice::getName, it -> it));
}
Konrad Borowski
la source
3

Voici la solution de StreamEx

StreamEx.of(choices).toMap(Choice::getName, c -> c);
user_3380739
la source
3
Map<String,Choice> map=list.stream().collect(Collectors.toMap(Choice::getName, s->s));

Sert même ce but pour moi,

Map<String,Choice> map=  list1.stream().collect(()-> new HashMap<String,Choice>(), 
            (r,s) -> r.put(s.getString(),s),(r,s) -> r.putAll(s));
Rajeev Akotkar
la source
3

Si chaque nouvelle valeur pour le même nom de clé doit être remplacée:

public Map < String, Choice > convertListToMap(List < Choice > choices) {
    return choices.stream()
        .collect(Collectors.toMap(Choice::getName,
            Function.identity(),
            (oldValue, newValue) - > newValue));
}

Si tous les choix doivent être regroupés dans une liste pour un nom:

public Map < String, Choice > convertListToMap(List < Choice > choices) {
    return choices.stream().collect(Collectors.groupingBy(Choice::getName));
}
Vaneet Kataria
la source
1
List<V> choices; // your list
Map<K,V> result = choices.stream().collect(Collectors.toMap(choice::getKey(),choice));
//assuming class "V" has a method to get the key, this method must handle case of duplicates too and provide a unique key.
vaibhav
la source
0

Comme alternative à guavaon peut utiliser kotlin-stdlib

private Map<String, Choice> nameMap(List<Choice> choices) {
    return CollectionsKt.associateBy(choices, Choice::getName);
}
Frank Neblung
la source
-1
String array[] = {"ASDFASDFASDF","AA", "BBB", "CCCC", "DD", "EEDDDAD"};
    List<String> list = Arrays.asList(array);
    Map<Integer, String> map = list.stream()
            .collect(Collectors.toMap(s -> s.length(), s -> s, (x, y) -> {
                System.out.println("Dublicate key" + x);
                return x;
            },()-> new TreeMap<>((s1,s2)->s2.compareTo(s1))));
    System.out.println(map);

Clé de publication AA {12 = ASDFASDFASDF, 7 = EEDDDAD, 4 = CCCC, 3 = BBB, 2 = AA}

Ajay Kumar
la source
2
Qu'est-ce que vous essayez de faire ici ?? Avez-vous lu la question?
navderm