Java 8 Lista <V> do mapy <K, V>

932

Chcę przetłumaczyć listę obiektów na mapę przy użyciu strumieni i lambd Java 3.

Tak napisałbym to w Javie 7 i niższych.

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

Mogę to łatwo osiągnąć za pomocą Java 8 i Guava, ale chciałbym wiedzieć, jak to zrobić bez Guava.

W Guava:

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

I Guawa z lambdami Java 8.

private Map<String, Choice> nameMap(List<Choice> choices) {
    return Maps.uniqueIndex(choices, Choice::getName);
}
Tom Cammann
źródło

Odpowiedzi:

1394

Na podstawie Collectorsdokumentacji jest to tak proste, jak:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName,
                                              Function.identity()));
zapl
źródło
167
Na marginesie, nawet po Javie 8, JDK nadal nie może konkurować w zwięzłości. Guava alternatywne wygląda o wiele czytelny: Maps.uniqueIndex(choices, Choice::getName).
Bogdan Calmac
3
Używając (importowanej statycznie) Seqz biblioteki JOOL (którą poleciłbym każdemu, kto używa Java 8), możesz również poprawić zwięzłość dzięki: seq(choices).toMap(Choice::getName)
lukens
5
Czy są jakieś korzyści z używania Function.identity? To znaczy - to jest krótsze
shabunc
9
@shabunc Nie znam żadnych korzyści i faktycznie korzystam z it -> itsiebie. Function.identity()jest tu używany głównie dlatego, że jest używany w przywołanej dokumentacji i to wszystko, co wiedziałem o lambdach w czasie pisania
zapl
12
@zapl, och, faktycznie okazuje się, że są
ku
307

Jeśli NIE gwarantujesz, że Twój klucz będzie unikalny dla wszystkich elementów na liście, powinieneś go przekonwertować na Map<String, List<Choice>>zamiastMap<String, Choice>

Map<String, List<Choice>> result =
 choices.stream().collect(Collectors.groupingBy(Choice::getName));
Ulises
źródło
76
To faktycznie daje Map <String, List <Choice>>, która dotyczy możliwości nieunikalnych kluczy, ale nie jest tym, czego zażądał OP. W Guava Multimaps.index (wybory, Choice :: getName) jest prawdopodobnie lepszą opcją, jeśli i tak tego chcesz.
Richard Nichols
lub raczej skorzystaj z Multimapy Guava <Ciąg, Wybór>, która jest całkiem przydatna w scenariuszach, w których ten sam klucz jest mapowany na wiele wartości. W Guava dostępnych jest wiele różnych narzędzi służących do korzystania z takich struktur danych zamiast tworzenia mapy <ciąg, lista <wybór>
Neeraj B.,
1
@RichardNichols dlaczego Multimapsmetoda guawy jest lepszą opcją? Może to być niedogodność, ponieważ nie zwraca Mapprzedmiotu.
oni
156

Użyj getName()jako klucza i Choicejako wartości mapy:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName, c -> c));
Ole
źródło
19
Napisz opis, aby użytkownik mógł zrozumieć.
Mayank Jain
4
Szkoda, że ​​nie ma tu więcej szczegółów, ponieważ najbardziej podoba mi się ta odpowiedź.
MadConan
Collectors.toMap(Choice::getName,c->c)(2 znaki krótsze)
Ferrybig
7
Jest równa choices.stream().collect(Collectors.toMap(choice -> choice.getName(),choice -> choice)); Pierwszej funkcji dla klucza, drugiej funkcji dla wartości
samochód wodny
16
Wiem, jak łatwo jest zobaczyć i zrozumieć, c -> cale Function.identity()zawiera więcej informacji semantycznych. Zwykle używam importu statycznego, więc mogę po prostu użyćidentity()
Hank D
28

Oto kolejna w przypadku, gdy nie chcesz używać Collectors.toMap ()

Map<String, Choice> result =
   choices.stream().collect(HashMap<String, Choice>::new, 
                           (m, c) -> m.put(c.getName(), c),
                           (m, u) -> {});
Emre Colak
źródło
1
Którego lepiej użyć wtedy Collectors.toMap()lub naszej własnej HashMap, jak pokazano w powyższym przykładzie?
Swapnil Gangrade
1
Ten przykład podał przykład, jak umieścić coś innego na mapie. Chciałem wartość nie dostarczoną przez wywołanie metody. Dzięki!
th3morg
1
Trzecia funkcja argumentu jest niepoprawna. Tam powinieneś zapewnić funkcję scalania dwóch Hashmap, coś w stylu Hashmap :: putAll
jesantana
25

Większość wymienionych odpowiedzi nie zawiera przypadku, gdy lista zawiera zduplikowane elementy . W takim przypadku pojawi się odpowiedź IllegalStateException. Zapoznaj się z poniższym kodem, aby obsługiwać również duplikaty list :

public Map<String, Choice> convertListToMap(List<Choice> choices) {
    return choices.stream()
        .collect(Collectors.toMap(Choice::getName, choice -> choice,
            (oldValue, newValue) -> newValue));
  }
Sahil Chhabra
źródło
19

Jeszcze jedna opcja w prosty sposób

Map<String,Choice> map = new HashMap<>();
choices.forEach(e->map.put(e.getName(),e));
Renukeswar
źródło
3
Nie ma realnej różnicy w korzystaniu z tego lub Java 7.
Ashish Lohia,
3
Łatwiej było mi to przeczytać i zrozumieć, co się działo niż inne mechanizmy
Freiheit,
2
SO poprosił o strumienie Java 8.
Mehraj Malik
18

Na przykład, jeśli chcesz przekonwertować pola obiektów na mapę:

Przykładowy obiekt:

class Item{
        private String code;
        private String name;

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

        //getters and setters
    }

I operacja przekształca Listę na mapę:

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.
źródło
12

Jeśli nie przeszkadza przy użyciu bibliotek 3rd party, AOL Cyklop reagują lib (ujawnienie jestem specjalista) posiada rozszerzenia dla wszystkich JDK Kolekcja typów, w tym listy i mapą .

ListX<Choices> choices;
Map<String, Choice> map = choices.toMap(c-> c.getName(),c->c);
John McClean
źródło
10

Próbowałem to zrobić i odkryłem, że korzystając z powyższych odpowiedzi, używając Functions.identity()klucza do mapy, miałem problemy z używaniem metody lokalnej, takiej jak this::localMethodNamefaktyczna praca z powodu problemów z pisaniem.

Functions.identity()faktycznie robi coś z pisaniem w tym przypadku, więc metoda zadziałałaby tylko przez zwrócenie Objecti zaakceptowanie parametruObject

Aby rozwiązać ten problem, skończyłem porzucając Functions.identity()i używając s->szamiast tego.

Tak więc mój kod, w moim przypadku do wyświetlenia wszystkich katalogów w katalogu, i dla każdego z nich użyj nazwy katalogu jako klucza do mapy, a następnie wywołaj metodę z nazwą katalogu i zwróć kolekcję elementów, wygląda następująco:

Map<String, Collection<ItemType>> items = Arrays.stream(itemFilesDir.listFiles(File::isDirectory))
.map(File::getName)
.collect(Collectors.toMap(s->s, this::retrieveBrandItems));
iZian
źródło
10

Możesz utworzyć Strumień indeksów za pomocą IntStream, a następnie przekonwertować je na mapę:

Map<Integer,Item> map = 
IntStream.range(0,items.size())
         .boxed()
         .collect(Collectors.toMap (i -> i, i -> items.get(i)));
Vikas Suryawanshi
źródło
1
Nie jest to dobra opcja, ponieważ wykonujesz wywołanie get () dla każdego elementu, a zatem zwiększasz złożoność operacji (o (n * k), jeśli elementy są skrótem).
Nicolas Nobelis,
Czy get (i) nie zawiera mapy skrótów O (1)?
Ivo van der Veeken,
@IvovanderVeeken get (i) we fragmencie kodu znajduje się na liście, a nie na mapie.
Zaki
@Zaki Mówiłem o uwadze Nicolasa. Nie widzę złożoności n * k, jeśli elementy są skrótem zamiast listy.
Ivo van der Veeken,
8

Napiszę, jak przekonwertować listę na mapę za pomocą ogólnych i inwersji kontroli . Po prostu uniwersalna metoda!

Może mamy listę liczb całkowitych lub listę obiektów. Pytanie brzmi zatem: co powinno być kluczem do mapy?

utwórz interfejs

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

teraz używając odwrócenia kontroli:

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

Na przykład, jeśli mamy obiekty książki, ta klasa ma wybrać klucz do mapy

public class BookKeyFinder implements KeyFinder<Long, Book> {
    @Override
    public Long getKey(Book e) {
        return e.getPrice()
    }
}
grep
źródło
6

Używam tej składni

Map<Integer, List<Choice>> choiceMap = 
choices.stream().collect(Collectors.groupingBy(choice -> choice.getName()));
user2069723
źródło
11
groupingBytworzy, a Map<K,List<V>>nie Map<K,V>.
Christoffer Hammarström
3
Odpowiedź duplikatu aplikacji. I String getName();(nie 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
źródło
4

Można to zrobić na 2 sposoby. Niech osoba będzie klasą, której użyjemy, aby to zademonstrować.

public class Person {

    private String name;
    private int age;

    public String getAge() {
        return age;
    }
}

Niech osoby będą listą Osób, które zostaną przekonwertowane na mapę

1.Za pomocą prostego foreach i wyrażenia lambda na liście

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

2.Używanie kolektorów w strumieniu zdefiniowanych na danej liście.

 Map<Integer,List<Person>> mapPersons = 
           persons.stream().collect(Collectors.groupingBy(Person::getAge));
raja emani
źródło
4

W tym celu można użyć strumieni. Aby usunąć potrzebę jawnego używania Collectors, można importować toMapstatycznie (zgodnie z zaleceniami Effective Java, wydanie trzecie).

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
źródło
3

Oto rozwiązanie StreamEx

StreamEx.of(choices).toMap(Choice::getName, c -> c);
użytkownik_3380739
źródło
3
Map<String,Choice> map=list.stream().collect(Collectors.toMap(Choice::getName, s->s));

Nawet mi to służy,

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
źródło
3

Jeśli należy zastąpić każdą nową wartość dla tej samej nazwy klucza:

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

Jeśli wszystkie opcje muszą być pogrupowane na liście dla nazwy:

public Map < String, Choice > convertListToMap(List < Choice > choices) {
    return choices.stream().collect(Collectors.groupingBy(Choice::getName));
}
Vaneet Kataria
źródło
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
źródło
0

Jako alternatywę guavamożna zastosować kotlin-stdlib

private Map<String, Choice> nameMap(List<Choice> choices) {
    return CollectionsKt.associateBy(choices, Choice::getName);
}
Frank Neblung
źródło
-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);

Dubluj klucz AA {12 = ASDFASDFASDF, 7 = EEDDDAD, 4 = CCCC, 3 = BBB, 2 = AA}

Ajay Kumar
źródło
2
Co próbujesz tutaj zrobić? Czy przeczytałeś pytanie?
navderm