Java: Cara mengonversi Daftar ke Peta


224

Baru-baru ini saya berbicara dengan seorang kolega tentang apa yang akan menjadi cara optimal untuk mengkonversi Listke MapJawa dan jika ada manfaat khusus untuk melakukannya.

Saya ingin tahu pendekatan konversi yang optimal dan akan sangat menghargai jika ada yang bisa membimbing saya.

Apakah pendekatan yang baik ini:

List<Object[]> results;
Map<Integer, String> resultsMap = new HashMap<Integer, String>();
for (Object[] o : results) {
    resultsMap.put((Integer) o[0], (String) o[1]);
}

2
Apa cara optimal terbaik? Optimalisasi dilakukan dengan mempertimbangkan parameter tertentu (kecepatan / memori).
Daniel Fath

6
Daftar berbeda dari Peta dengan cara konseptual - Peta memiliki gagasan tentang pasangan 'kunci, nilai', sedangkan Daftar tidak. Mengingat hal ini, tidak jelas bagaimana tepatnya Anda akan mengkonversi dari Daftar ke Peta dan kembali.
Victor Sorokin

1
@Aniel: By Optimal, saya maksudkan apa cara terbaik untuk melakukannya di antara semua cara yang berbeda antara saya tidak yakin dengan semua cara dan akan lebih baik untuk melihat beberapa cara mengubah daftar menjadi peta.
Rachel


Jawaban:


188
List<Item> list;
Map<Key,Item> map = new HashMap<Key,Item>();
for (Item i : list) map.put(i.getKey(),i);

Dengan asumsi tentu saja setiap Item memiliki getKey()metode yang mengembalikan kunci dari jenis yang tepat.


1
Anda juga dapat memasukkan posisi dalam daftar.
Jeremy

@ Jim: Apakah saya perlu mengatur getKey()parameter tertentu?
Rachel

Juga apa yang akan menjadi nilai dalam Peta, dapatkah Anda menguraikan contoh?
Rachel

1
@Rachel - Nilainya adalah item dalam daftar, dan kuncinya adalah sesuatu yang membuat item tersebut unik, yang ditentukan oleh Anda. Penggunaan Jim getKey()sewenang-wenang.
Jeremy

1
Anda mengetahui ukuran sebelumnya sehingga Anda dapat melakukan Peta <Kunci, Item> map = HashMap baru <Kunci, Item> (list.size ());
Víctor Romero

316

Dengan , Anda dapat melakukan ini dalam satu baris menggunakan aliran , dan Collectorskelas.

Map<String, Item> map = 
    list.stream().collect(Collectors.toMap(Item::getKey, item -> item));

Demo singkat:

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Test{
    public static void main (String [] args){
        List<Item> list = IntStream.rangeClosed(1, 4)
                                   .mapToObj(Item::new)
                                   .collect(Collectors.toList()); //[Item [i=1], Item [i=2], Item [i=3], Item [i=4]]

        Map<String, Item> map = 
            list.stream().collect(Collectors.toMap(Item::getKey, item -> item));

        map.forEach((k, v) -> System.out.println(k + " => " + v));
    }
}
class Item {

    private final int i;

    public Item(int i){
        this.i = i;
    }

    public String getKey(){
        return "Key-"+i;
    }

    @Override
    public String toString() {
        return "Item [i=" + i + "]";
    }
}

Keluaran:

Key-1 => Item [i=1]
Key-2 => Item [i=2]
Key-3 => Item [i=3]
Key-4 => Item [i=4]

Seperti disebutkan dalam komentar, Anda dapat menggunakan Function.identity()alih-alih item -> item, meskipun saya menemukan i -> iagak eksplisit.

Dan untuk menjadi catatan lengkap bahwa Anda dapat menggunakan operator biner jika fungsi Anda tidak bijective. Sebagai contoh mari kita pertimbangkan ini Listdan fungsi pemetaan yang untuk nilai int, hitung hasilnya modulo 3:

List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6);
Map<String, Integer> map = 
    intList.stream().collect(toMap(i -> String.valueOf(i % 3), i -> i));

Saat menjalankan kode ini, Anda akan mendapatkan pesan kesalahan java.lang.IllegalStateException: Duplicate key 1. Ini karena 1% 3 sama dengan 4% 3 dan karenanya memiliki nilai kunci yang sama mengingat fungsi pemetaan kunci. Dalam hal ini Anda dapat menyediakan operator gabungan.

Inilah salah satu yang menjumlahkan nilai-nilai; (i1, i2) -> i1 + i2;yang dapat diganti dengan referensi metode Integer::sum.

Map<String, Integer> map = 
    intList.stream().collect(toMap(i -> String.valueOf(i % 3), 
                                   i -> i, 
                                   Integer::sum));

yang sekarang menghasilkan:

0 => 9 (i.e 3 + 6)
1 => 5 (i.e 1 + 4)
2 => 7 (i.e 2 + 5)

Semoga ini bisa membantu! :)


19
lebih baik digunakan Function.identity()daripadaitem -> item
Emmanuel Touzery

1
@ EmmanuelTouzery Baiklah, Function.identity()kembali t -> t;.
Alexis C.

2
Tentu, keduanya bekerja. Saya kira ini masalah selera. Saya menemukan Function.identity () lebih mudah dikenali.
Emmanuel Touzery

OP tidak menangani semua pojo, hanya string dan integer yang tidak dapat Anda kal ::getKey.
phil294

@ Blauhirn Saya tahu, contoh saya didasarkan pada kelas khusus tepat di bawah. Anda bebas menggunakan fungsi apa pun untuk menghasilkan kunci dari nilai.
Alexis C.

115

Kalau-kalau pertanyaan ini tidak ditutup sebagai duplikat, jawaban yang tepat adalah menggunakan Google Collections :

Map<String,Role> mappedRoles = Maps.uniqueIndex(yourList, new Function<Role,String>() {
  public String apply(Role from) {
    return from.getName(); // or something else
  }});

17
Ini harus di atas
Martin Andersson

6
" Guava berisi superset yang sepenuhnya kompatibel dari Perpustakaan Koleksi Google yang lama dan sudah usang . Anda seharusnya tidak menggunakan perpustakaan itu lagi. " Pembaruan mungkin diperlukan.
Tiny

3
Penggunaan pustaka eksternal untuk operasi sederhana semacam itu adalah berlebihan. Itu atau tanda perpustakaan standar yang sangat lemah. Dalam hal ini jawaban @ jim-garrison sangat masuk akal. Sangat menyedihkan bahwa java tidak memiliki metode yang membantu seperti "peta" dan "mengurangi" tetapi tidak sepenuhnya diperlukan.
linuxdan

2
Ini menggunakan Jambu Biji. Sayangnya Guava super lambat di Android, jadi solusi ini tidak boleh digunakan dalam proyek Android.
IgorGanapolsky

jika item dalam daftar memiliki duplikat roleNames, kode di atas akan melempar pengecualian,
Junchen Liu

17

Menggunakan Java 8 Anda dapat melakukan hal berikut:

Map<Key, Value> result= results
                       .stream()
                       .collect(Collectors.toMap(Value::getName,Function.identity()));

Value dapat berupa objek apa pun yang Anda gunakan.


16

Sejak Java 8, jawaban oleh @ZouZou menggunakan Collectors.toMapkolektor tentu cara idiomatis untuk menyelesaikan masalah ini.

Dan karena ini adalah tugas yang umum, kita dapat membuatnya menjadi utilitas statis.

Dengan begitu solusinya benar-benar menjadi satu garis.

/**
 * Returns a map where each entry is an item of {@code list} mapped by the
 * key produced by applying {@code mapper} to the item.
 *
 * @param list the list to map
 * @param mapper the function to produce the key from a list item
 * @return the resulting map
 * @throws IllegalStateException on duplicate key
 */
public static <K, T> Map<K, T> toMapBy(List<T> list,
        Function<? super T, ? extends K> mapper) {
    return list.stream().collect(Collectors.toMap(mapper, Function.identity()));
}

Dan inilah cara Anda menggunakannya pada List<Student>:

Map<Long, Student> studentsById = toMapBy(students, Student::getId);

Untuk diskusi tentang parameter tipe metode ini, lihat pertanyaan tindak lanjut saya .
glts

Ini akan melempar pengecualian jika ada kunci duplikat. Seperti: Pengecualian di utas "utama" java.lang.IllegalStateException: Kunci duplikat .... Untuk detail, lihat: codecramp.com/java-8-streams-api-convert-list-map
EMM

@EMM Tentu saja, sebagaimana dimaksud dan didokumentasikan di Javadoc.
glts

Ya, perbarui jawaban untuk menutupi kasus duplikat. Silakan tinjau. Terima kasih
EMM

10

A Listdan Mapsecara konseptual berbeda. A Listadalah koleksi barang yang dipesan. Item dapat berisi duplikat, dan item mungkin tidak memiliki konsep pengidentifikasi unik (kunci). Nilai A Maptelah dipetakan ke tombol. Setiap kunci hanya dapat menunjuk ke satu nilai.

Oleh karena itu, tergantung pada Listitem Anda, mungkin atau tidak mungkin untuk mengubahnya menjadi Map. Apakah Listbarang Anda tidak memiliki duplikat? Apakah setiap item memiliki kunci unik? Jika demikian maka mungkin untuk menempatkannya di Map.


10

Alexis telah mengirim jawaban di Java 8 menggunakan metode toMap(keyMapper, valueMapper). Sesuai dokumen untuk penerapan metode ini:

Tidak ada jaminan pada jenis, mutabilitas, serializability, atau keamanan thread dari Peta yang dikembalikan.

Jadi jika kita tertarik pada implementasi spesifik dari Mapantarmuka misalnya HashMapmaka kita dapat menggunakan formulir kelebihan beban sebagai:

Map<String, Item> map2 =
                itemList.stream().collect(Collectors.toMap(Item::getKey, //key for map
                        Function.identity(),    // value for map
                        (o,n) -> o,             // merge function in case of conflict with keys
                        HashMap::new));         // map factory - we want HashMap and not any Map implementation

Meskipun menggunakan salah satu Function.identity()atau baik - i->ibaik saja tetapi tampaknya Function.identity()bukannya i -> imenghemat beberapa memori sesuai jawaban terkait ini .


1
Fakta lucu bahwa pada tahun 2019 sejumlah besar orang masih tidak menyadari bahwa mereka tidak tahu implementasi Peta nyata yang mereka dapatkan dengan lambda! Sebenarnya ini hanya satu jawaban yang saya temukan dengan Java 8 lambdas yang akan saya gunakan untuk produksi.
Pavlo

Apakah ada cara untuk mengumpulkan dengan menentukan tipe peta tetapi tanpa menggunakan fungsi gabungan?
Rosberg Linhares


5

Metode universal

public static <K, V> Map<K, V> listAsMap(Collection<V> sourceList, ListToMapConverter<K, V> converter) {
    Map<K, V> newMap = new HashMap<K, V>();
    for (V item : sourceList) {
        newMap.put( converter.getKey(item), item );
    }
    return newMap;
}

public static interface ListToMapConverter<K, V> {
    public K getKey(V item);
}

Bagaimana cara menggunakan ini? Apa yang harus saya berikan sebagai converterparameter dalam metode ini?
IgorGanapolsky

4

Tanpa java-8, Anda dapat melakukan ini dalam satu koleksi baris Commons, dan kelas Penutupan

List<Item> list;
@SuppressWarnings("unchecked")
Map<Key, Item> map  = new HashMap<Key, Item>>(){{
    CollectionUtils.forAllDo(list, new Closure() {
        @Override
        public void execute(Object input) {
            Item item = (Item) input;
            put(i.getKey(), item);
        }
    });
}};

2

Banyak solusi muncul di pikiran, tergantung pada apa yang ingin Anda capai:

Setiap item Daftar adalah kunci dan nilai

for( Object o : list ) {
    map.put(o,o);
}

Elemen daftar memiliki sesuatu untuk dicari, mungkin sebuah nama:

for( MyObject o : list ) {
    map.put(o.name,o);
}

Elemen daftar memiliki sesuatu untuk dicari, dan tidak ada jaminan bahwa mereka unik: Gunakan MultiMaps Googles

for( MyObject o : list ) {
    multimap.put(o.name,o);
}

Memberikan semua elemen posisi sebagai kunci:

for( int i=0; i<list.size; i++ ) {
    map.put(i,list.get(i));
}

...

Itu benar-benar tergantung pada apa yang ingin Anda capai.

Seperti yang dapat Anda lihat dari contoh, Peta adalah pemetaan dari kunci ke nilai, sementara daftar hanyalah serangkaian elemen yang masing-masing memiliki posisi. Jadi mereka tidak dapat secara otomatis dikonversi.


Tetapi kita dapat mempertimbangkan posisi Elemen Daftar sebagai kunci dan memberi nilai pada peta, apakah ini solusi yang baik?
Rachel

AFAIK ya! Tidak ada fungsi di JDK yang melakukan itu secara otomatis, jadi Anda harus memutar sendiri.
Daniel

apakah mungkin untuk melakukan versi terakhir (menggunakan indeks array sebagai kunci peta) dengan java 8 stream?
phil294

2

Inilah sedikit metode yang saya tulis untuk tujuan ini. Ini menggunakan Validasi dari Apache Commons.

Jangan ragu untuk menggunakannya.

/**
 * Converts a <code>List</code> to a map. One of the methods of the list is called to retrive
 * the value of the key to be used and the object itself from the list entry is used as the
 * objct. An empty <code>Map</code> is returned upon null input.
 * Reflection is used to retrieve the key from the object instance and method name passed in.
 *
 * @param <K> The type of the key to be used in the map
 * @param <V> The type of value to be used in the map and the type of the elements in the
 *            collection
 * @param coll The collection to be converted.
 * @param keyType The class of key
 * @param valueType The class of the value
 * @param keyMethodName The method name to call on each instance in the collection to retrieve
 *            the key
 * @return A map of key to value instances
 * @throws IllegalArgumentException if any of the other paremeters are invalid.
 */
public static <K, V> Map<K, V> asMap(final java.util.Collection<V> coll,
        final Class<K> keyType,
        final Class<V> valueType,
        final String keyMethodName) {

    final HashMap<K, V> map = new HashMap<K, V>();
    Method method = null;

    if (isEmpty(coll)) return map;
    notNull(keyType, Messages.getString(KEY_TYPE_NOT_NULL));
    notNull(valueType, Messages.getString(VALUE_TYPE_NOT_NULL));
    notEmpty(keyMethodName, Messages.getString(KEY_METHOD_NAME_NOT_NULL));

    try {
        // return the Method to invoke to get the key for the map
        method = valueType.getMethod(keyMethodName);
    }
    catch (final NoSuchMethodException e) {
        final String message =
            String.format(
                    Messages.getString(METHOD_NOT_FOUND),
                    keyMethodName,
                    valueType);
        e.fillInStackTrace();
        logger.error(message, e);
        throw new IllegalArgumentException(message, e);
    }
    try {
        for (final V value : coll) {

            Object object;
            object = method.invoke(value);
            @SuppressWarnings("unchecked")
            final K key = (K) object;
            map.put(key, value);
        }
    }
    catch (final Exception e) {
        final String message =
            String.format(
                    Messages.getString(METHOD_CALL_FAILED),
                    method,
                    valueType);
        e.fillInStackTrace();
        logger.error(message, e);
        throw new IllegalArgumentException(message, e);
    }
    return map;
}

2

Anda dapat memanfaatkan aliran stream Java 8.

public class ListToMap {

  public static void main(String[] args) {
    List<User> items = Arrays.asList(new User("One"), new User("Two"), new User("Three"));

    Map<String, User> map = createHashMap(items);
    for(String key : map.keySet()) {
      System.out.println(key +" : "+map.get(key));
    }
  }

  public static Map<String, User> createHashMap(List<User> items) {
    Map<String, User> map = items.stream().collect(Collectors.toMap(User::getId, Function.identity()));
    return map;
  }
}

Untuk detail lebih lanjut kunjungi: http://codecramp.com/java-8-streams-api-convert-list-map/


1

Contoh Java 8 untuk mengubah List<?>objek menjadi Map<k, v>:

List<Hosting> list = new ArrayList<>();
list.add(new Hosting(1, "liquidweb.com", new Date()));
list.add(new Hosting(2, "linode.com", new Date()));
list.add(new Hosting(3, "digitalocean.com", new Date()));

//example 1
Map<Integer, String> result1 = list.stream().collect(
    Collectors.toMap(Hosting::getId, Hosting::getName));

System.out.println("Result 1 : " + result1);

//example 2
Map<Integer, String> result2 = list.stream().collect(
    Collectors.toMap(x -> x.getId(), x -> x.getName()));

Kode disalin dari:
https://www.mkyong.com/java8/java-8-convert-list-to-map/


1

seperti yang sudah dikatakan, di java-8 kami memiliki solusi ringkas oleh Kolektor:

  list.stream().collect(
         groupingBy(Item::getKey)
        )

dan juga, Anda dapat membuat sarang beberapa grup yang melewati metode pengelompokan lainnya sebagai parameter kedua:

  list.stream().collect(
         groupingBy(Item::getKey, groupingBy(Item::getOtherKey))
        )

Dengan cara ini, kita akan memiliki peta multi level, seperti ini: Map<key, Map<key, List<Item>>>


1

Menggunakan aliran java-8

results.stream().collect(Collectors.toMap(e->((Integer)e[0]), e->(String)e[1]));

0

Saya suka jawaban Kango_V, tapi saya pikir itu terlalu rumit. Saya pikir ini lebih sederhana - mungkin terlalu sederhana. Jika cenderung, Anda dapat mengganti String dengan penanda Generik, dan membuatnya berfungsi untuk semua jenis Kunci.

public static <E> Map<String, E> convertListToMap(Collection<E> sourceList, ListToMapConverterInterface<E> converterInterface) {
    Map<String, E> newMap = new HashMap<String, E>();
    for( E item : sourceList ) {
        newMap.put( converterInterface.getKeyForItem( item ), item );
    }
    return newMap;
}

public interface ListToMapConverterInterface<E> {
    public String getKeyForItem(E item);
}

Digunakan seperti ini:

        Map<String, PricingPlanAttribute> pricingPlanAttributeMap = convertListToMap( pricingPlanAttributeList,
                new ListToMapConverterInterface<PricingPlanAttribute>() {

                    @Override
                    public String getKeyForItem(PricingPlanAttribute item) {
                        return item.getFullName();
                    }
                } );

0

Apache Commons MapUtils.populateMap

Jika Anda tidak menggunakan Java 8 dan Anda tidak ingin menggunakan loop eksplisit untuk beberapa alasan, coba MapUtils.populateMapdari Apache Commons.

MapUtils.populateMap

Katakanlah Anda memiliki daftar Pairs.

List<ImmutablePair<String, String>> pairs = ImmutableList.of(
    new ImmutablePair<>("A", "aaa"),
    new ImmutablePair<>("B", "bbb")
);

Dan Anda sekarang ingin Peta Pairkunci untuk Pairobjek.

Map<String, Pair<String, String>> map = new HashMap<>();
MapUtils.populateMap(map, pairs, new Transformer<Pair<String, String>, String>() {

  @Override
  public String transform(Pair<String, String> input) {
    return input.getKey();
  }
});

System.out.println(map);

memberikan output:

{A=(A,aaa), B=(B,bbb)}

Yang sedang berkata, forloop mungkin lebih mudah dipahami. (Ini di bawah ini memberikan output yang sama):

Map<String, Pair<String, String>> map = new HashMap<>();
for (Pair<String, String> pair : pairs) {
  map.put(pair.getKey(), pair);
}
System.out.println(map);
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.