Java 8 Daftar <V> ke Peta <K, V>

933

Saya ingin menerjemahkan Daftar objek ke dalam Peta menggunakan aliran Java 8 dan lambdas.

Ini adalah bagaimana saya akan menulisnya di Java 7 dan di bawah.

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

Saya dapat melakukannya dengan mudah menggunakan Java 8 dan Jambu tetapi saya ingin tahu bagaimana melakukan ini tanpa Jambu.

Dalam jambu biji:

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

Dan Jambu biji dengan Java 8 lambdas.

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

Jawaban:

1395

Berdasarkan Collectorsdokumentasi itu sesederhana:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName,
                                              Function.identity()));
zapl
sumber
167
Sebagai catatan, bahkan setelah Java 8, JDK masih tidak dapat bersaing dalam kontes singkat. Jambu terlihat alternatif begitu banyak dibaca: Maps.uniqueIndex(choices, Choice::getName).
Bogdan Calmac
3
Menggunakan (diimpor secara statis) Seqdari perpustakaan JOOL (yang saya sarankan kepada siapa pun yang menggunakan Java 8), Anda juga dapat meningkatkan singkatnya dengan: seq(choices).toMap(Choice::getName)
lukens
5
Apakah ada manfaat dari menggunakan Function.identity? Maksud saya, ini -> lebih pendek
shabunc
9
@shabunc Saya tidak tahu manfaat apa pun dan benar-benar menggunakan it -> itdiri saya sendiri. Function.identity()digunakan di sini sebagian besar karena itu digunakan dalam dokumentasi yang direferensikan dan hanya itu yang saya ketahui tentang lambdas pada saat penulisan
zapl
12
@ zapl, oh, sebenarnya ternyata ada alasan di balik ini - stackoverflow.com/questions/28032827/…
shabunc
307

Jika kunci Anda TIDAK dijamin unik untuk semua elemen dalam daftar, Anda harus mengonversinya menjadi Map<String, List<Choice>>aMap<String, Choice>

Map<String, List<Choice>> result =
 choices.stream().collect(Collectors.groupingBy(Choice::getName));
Ulises
sumber
76
Ini benar-benar memberi Anda Peta <String, Daftar <Pilihan>> yang berkaitan dengan kemungkinan kunci non-unik, tetapi bukan apa yang diminta OP. Di Guava, Multimaps.index (pilihan, Pilihan :: getName) mungkin merupakan opsi yang lebih baik jika ini yang Anda inginkan.
Richard Nichols
atau lebih tepatnya menggunakan Multimap <String, Choice> Guava yang cukup berguna dalam skenario di mana kunci yang sama memetakan ke beberapa nilai. Ada berbagai metode utilitas yang tersedia di Guava untuk menggunakan struktur data seperti itu daripada membuat Map <String, List <Choice>>
Neeraj B.
1
@ RichardNichols mengapa Multimapsmetode jambu biji pilihan yang lebih baik? Ini bisa menjadi ketidaknyamanan karena tidak mengembalikan MapObyek.
theyuv
156

Gunakan getName()sebagai kunci dan Choiceitu sendiri sebagai nilai peta:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName, c -> c));
Ole
sumber
19
Harap tulis deskripsi agar pengguna dapat mengerti.
Mayank Jain
4
Sayang sekali tidak ada rincian lebih lanjut di sini, karena saya suka jawaban ini terbaik.
MadConan
Collectors.toMap(Choice::getName,c->c)(2 chars lebih pendek)
Ferrybig
7
Ini sama dengan choices.stream().collect(Collectors.toMap(choice -> choice.getName(),choice -> choice)); fungsi pertama untuk kunci, fungsi kedua untuk nilai
waterscar
16
Saya tahu betapa mudahnya untuk melihat dan memahami c -> ctetapi Function.identity()membawa lebih banyak informasi semantik. Saya biasanya menggunakan impor statis sehingga saya bisa menggunakannyaidentity()
Hank D
28

Berikut ini satu lagi jika Anda tidak ingin menggunakan Collectors.toMap ()

Map<String, Choice> result =
   choices.stream().collect(HashMap<String, Choice>::new, 
                           (m, c) -> m.put(c.getName(), c),
                           (m, u) -> {});
Emre Colak
sumber
1
Mana yang lebih baik untuk digunakan Collectors.toMap()atau HashMap kita sendiri seperti yang Anda tunjukkan dalam contoh di atas?
Swapnil Gangrade
1
Contoh ini memberikan contoh cara menempatkan sesuatu di peta. Saya ingin nilai yang tidak disediakan oleh pemanggilan metode. Terima kasih!
th3morg
1
Fungsi argumen ketiga tidak benar. Di sana Anda harus menyediakan beberapa fungsi untuk menggabungkan dua Hashmaps, sesuatu seperti Hashmap :: putAll
jesantana
25

Sebagian besar jawaban yang tercantum, ketinggalan kasus ketika daftar memiliki item duplikat . Dalam hal itu ada jawaban akan melempar IllegalStateException. Lihat kode di bawah ini untuk menangani duplikat daftar juga:

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

Satu lagi opsi dengan cara sederhana

Map<String,Choice> map = new HashMap<>();
choices.forEach(e->map.put(e.getName(),e));
Renukeswar
sumber
3
Tidak ada perbedaan yang layak dalam menggunakan jenis ini atau java 7.
Ashish Lohia
3
Saya menemukan ini lebih mudah untuk membaca dan memahami apa yang sedang terjadi daripada mekanisme lainnya
Freiheit
2
SO bertanya dengan Java 8 Streams.
Mehraj Malik
18

Misalnya, jika Anda ingin mengonversi bidang objek ke peta:

Contoh objek:

class Item{
        private String code;
        private String name;

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

        //getters and setters
    }

Dan operasi konversi Daftar Ke Peta:

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
sumber
12

Jika Anda tidak keberatan menggunakan perpustakaan pihak ketiga, cyclop-react lib (pengungkapan Saya adalah kontributor) AOL memiliki ekstensi untuk semua jenis Koleksi JDK , termasuk Daftar dan Peta .

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

Saya mencoba melakukan ini dan menemukan bahwa, menggunakan jawaban di atas, ketika menggunakan Functions.identity()untuk kunci Peta, maka saya memiliki masalah dengan menggunakan metode lokal suka this::localMethodNamebenar-benar bekerja karena masalah pengetikan.

Functions.identity()sebenarnya melakukan sesuatu pada pengetikan dalam kasus ini sehingga metode hanya akan bekerja dengan mengembalikan Objectdan menerima paramObject

Untuk mengatasi ini, saya akhirnya membuang Functions.identity()dan menggunakan s->ssaja.

Jadi kode saya, dalam kasus saya untuk mendaftar semua direktori di dalam direktori, dan untuk masing-masing menggunakan nama direktori sebagai kunci untuk peta dan kemudian memanggil metode dengan nama direktori dan mengembalikan koleksi item, terlihat seperti:

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

Anda dapat membuat Stream indeks menggunakan IntStream dan kemudian mengonversinya menjadi Peta:

Map<Integer,Item> map = 
IntStream.range(0,items.size())
         .boxed()
         .collect(Collectors.toMap (i -> i, i -> items.get(i)));
Vikas Suryawanshi
sumber
1
Ini bukan opsi yang baik karena Anda melakukan get () panggilan untuk setiap elemen, karenanya meningkatkan kompleksitas operasi (o (n * k) jika item adalah peta hash).
Nicolas Nobelis
Tidak mendapatkan (i) pada hashmap O (1)?
Ivo van der Veeken
@IvovanderVeeken get (i) dalam cuplikan kode ada di daftar, bukan di peta.
Zaki
@ Zaki saya berbicara tentang ucapan Nicolas. Saya tidak melihat kerumitan n * k jika item adalah hashmap dan bukan daftar.
Ivo van der Veeken
8

Saya akan menulis bagaimana mengkonversi daftar ke peta menggunakan generik dan inversi kontrol . Hanya metode universal!

Mungkin kita memiliki daftar Integer atau daftar objek. Jadi pertanyaannya adalah sebagai berikut: apa yang harus menjadi kunci peta?

buat antarmuka

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

sekarang menggunakan inversi kontrol:

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

Sebagai contoh, jika kita memiliki objek buku, kelas ini adalah untuk memilih kunci untuk peta

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

Saya menggunakan sintaks ini

Map<Integer, List<Choice>> choiceMap = 
choices.stream().collect(Collectors.groupingBy(choice -> choice.getName()));
pengguna2069723
sumber
11
groupingBymenciptakan Map<K,List<V>>, bukan a Map<K,V>.
Christoffer Hammarström
3
Dup dari ulises menjawab. Dan, String getName();(bukan 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
sumber
4

Ini bisa dilakukan dengan 2 cara. Biarkan orang menjadi kelas yang akan kita gunakan untuk menunjukkannya.

public class Person {

    private String name;
    private int age;

    public String getAge() {
        return age;
    }
}

Biarkan orang menjadi daftar Orang yang akan dikonversi ke peta

1.Menggunakan Simple foreach dan Ekspresi Lambda pada Daftar

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

2.Menggunakan Kolektor pada Streaming yang ditentukan pada Daftar yang diberikan.

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

Dimungkinkan untuk menggunakan stream untuk melakukan ini. Untuk menghapus kebutuhan untuk menggunakan secara eksplisit Collectors, impor mungkin dilakukan toMapsecara statis (seperti yang disarankan oleh Java Efektif, edisi ketiga).

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
sumber
3

Berikut ini solusi dari StreamEx

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

Bahkan melayani tujuan ini untukku,

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
sumber
3

Jika setiap nilai baru untuk nama kunci yang sama harus diganti:

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

Jika semua pilihan harus dikelompokkan dalam daftar untuk suatu nama:

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

Sebagai alternatif, Anda guavadapat menggunakan kotlin-stdlib

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

Kunci publikasikan AA {12 = ASDFASDFASDF, 7 = EEDDDAD, 4 = CCCC, 3 = BBB, 2 = AA}

Ajay Kumar
sumber
2
Apa yang kamu coba lakukan di sini ?? Apakah Anda membaca pertanyaannya?
navderm