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]);
}
Rachel
sumber
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.

Jim Garrison
sumber
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! :)

Alexis C.
sumber
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
  }});
ripper234
sumber
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.

stackFan
sumber
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);
glts
sumber
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.

Steve Kuo
sumber
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 .

akhil_mittal
sumber
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);
}
xxf
sumber
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);
        }
    });
}};
Vitaliy Oliynyk
sumber
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.

Daniel
sumber
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;
}
Kango_V
sumber
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/

EMM
sumber
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/

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

Andrea Scarafoni
sumber
1

Menggunakan aliran java-8

results.stream().collect(Collectors.toMap(e->((Integer)e[0]), e->(String)e[1]));
Ankit Sharma
sumber
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();
                    }
                } );
cs94njw
sumber
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);
kesalahan ketik
sumber