Java: Listeyi Haritaya Dönüştürme


224

Son zamanlarda dönüştürmek için en uygun yol ne olurdu hakkında bir meslektaşı ile bir görüşme yapmak Listiçin MapJava ve eğer orada bunu yaparken herhangi bir spesifik yararlanır.

Optimum dönüşüm yaklaşımını bilmek istiyorum ve herhangi biri bana rehberlik edebilirse gerçekten minnettar olurum.

Bu iyi bir yaklaşım mı:

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

2
En iyi optimum yol nedir? Optimizasyon, belirli bir parametre (hız / bellek) göz önünde bulundurularak yapılır.
Daniel Fath

6
Liste Kavramsal olarak Haritadan farklıdır - Harita 'anahtar, değer' çifti kavramına sahipken, Liste değildir. Bu göz önüne alındığında, tam olarak nasıl listeden haritaya ve geri dönüş yapacağınız belirsizdir.
Victor Sorokin

1
@Daniel: Optimal'e göre, tüm yollardan emin olmadığım arasındaki tüm farklı yollar arasında bunu yapmanın en iyi yolunun ne olduğunu kastettim ve bu yüzden listeyi haritaya dönüştürmenin bazı farklı yollarını görmek iyi olurdu.
Rachel


Yanıtlar:


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

Elbette her bir Maddenin getKey()uygun tipte bir anahtar döndüren bir yöntemi olduğu varsayılarak .


1
Listedeki konumu da girebilirsiniz.
Jeremy

@Jim: getKey()Belirli bir parametreye ayarlamam gerekir mi?
Rachel

Ayrıca Haritadaki değer ne olurdu, bir örnekle açıklayabilir misiniz?
Rachel

1
@Rachel - Değer, listedeki öğedir ve anahtar, öğeyi sizin tarafınızdan belirlenen benzersiz kılan bir şeydir. Jim'in kullanımı getKey()keyfi idi.
Jeremy

1
Önceden boyutu biliyorsunuz, böylece Map <Key, Item> map = new HashMap <Key, Item> (list.size ());
Víctor Romero

316

İle , akışları ve Collectorssınıfı kullanarak bunu tek bir satırda yapabilirsiniz .

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

Kısa demo:

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 + "]";
    }
}

Çıktı:

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

Yorumlarda belirtildiği gibi , oldukça açık bulmama rağmen, Function.identity()yerine kullanabilirsiniz .item -> itemi -> i

Ve tam olarak belirtmek gerekirse, fonksiyonunuz iki yönlü değilse ikili bir operatör kullanabilirsiniz. Örneğin, bunu Listve bir int değeri için modulo 3'ün sonucunu hesaplayan eşleme işlevini ele alalım :

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

Bu kodu çalıştırırken bir hata mesajı alırsınız java.lang.IllegalStateException: Duplicate key 1. Bunun nedeni,% 1 3'ün% 4 ile aynı olması ve bu nedenle anahtar eşleme işlevi verildiğinde aynı anahtar değerine sahip olmasıdır. Bu durumda bir birleştirme operatörü sağlayabilirsiniz.

İşte değerleri toplayan; (i1, i2) -> i1 + i2;yöntem referansı ile değiştirilebilir Integer::sum.

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

şimdi çıktı:

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

Umarım yardımcı olur! :)


19
Function.identity()yerine daha iyi kullanımitem -> item
Emmanuel Touzery

1
@EmmanuelTouzery Peki, Function.identity()geri döner t -> t;.
Alexis C.

2
Tabii, ikisi de işe yarıyor. Sanırım bu bir zevk meselesi. Function.identity () işlevini daha çabuk tanınabilir buluyorum.
Emmanuel Touzery

OP, herhangi bir pojosla uğraşmaz, sadece üzerinde duramayacağınız dizeler ve tamsayı ::getKey.
phil294

@Blauhirn Biliyorum, örneğim hemen altındaki özel sınıfa dayanıyor. Değerlerinizden anahtarlarınızı oluşturmak için istediğiniz işlevi kullanabilirsiniz.
Alexis C.

115

Bu sorunun kopya olarak kapatılmaması durumunda , doğru cevap Google Koleksiyonlarını kullanmaktır :

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

17
Bu en tepede olmalı
Martin Andersson

6
" Guava , eski, kullanımdan kaldırılmış Google Koleksiyonlar Kitaplığı'nın tam uyumlu bir üst kümesini içeriyor . Artık bu kitaplığı kullanmamalısınız. " Bir güncelleme gerekebilir . "
Küçük

3
Bu kadar basit bir işlem için harici bir kitaplığın kullanılması aşırıdır. Bu ya da çok zayıf bir standart kütüphanenin işareti. Bu durumda @ jim-garrison'un cevabı son derece makul. Java'nın "harita" ve "azalt" gibi yararlı yöntemlere sahip olmaması üzücü ama tamamen gerekli değil.
linuxdan

2
Bu Guava kullanır. Ne yazık ki Guava Android'de süper yavaş, bu nedenle bu çözüm bir Android projesinde kullanılmamalıdır.
IgorGanapolsky

listedeki öğe yinelenen roleNames varsa, yukarıdaki kod istisna atar,
Junchen Liu

17

Java 8'i kullanarak aşağıdakileri yapabilirsiniz:

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

Value kullandığınız herhangi bir nesne olabilir.


16

Java 8, @ZouZou tarafından cevap kullanılarakCollectors.toMap toplayıcıyı kesinlikle bu sorunu çözmenin deyimsel yoludur.

Ve bu çok yaygın bir görev olduğundan, bunu statik bir yardımcı program haline getirebiliriz.

Bu şekilde çözüm gerçekten tek katmanlı olur.

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

Ve bunu aşağıdaki gibi kullanabilirsiniz List<Student>:

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

Bu yöntemin tip parametreleri hakkında bir tartışma için takip soruma bakın .
glts

Yinelenen anahtarlar olması durumunda bu bir istisna atar. Sth gibi: "main" iş parçacığında istisna java.lang.IllegalStateException: Yinelenen anahtar .... Ayrıntılar için bkz. Codecramp.com/java-8-streams-api-convert-list-map
EMM

@EMM Tabii ki, Javadoc'ta amaçlandığı ve belgelendiği gibi.
glts

Evet, kopyaları kapsayan cevabı güncelledi. Gözden geçirin. Teşekkürler
EMM

10

A Listve Mapkavramsal olarak farklıdır. A List, öğelerin sıralı bir koleksiyonudur. Öğeler kopyalar içerebilir ve bir öğenin benzersiz bir tanımlayıcı (anahtar) kavramı olmayabilir. A Map, anahtarlarla eşlenmiş değerlere sahiptir. Her tuş yalnızca bir değere işaret edebilir.

Bu nedenle, Listöğelerinize bağlı olarak, öğenizi a dönüştürmeniz mümkün olabilir veya olmayabilir Map. Senin mu Listbireyin öğeler çiftleri var? Her öğenin benzersiz bir anahtarı var mı? Eğer öyleyse, onları a Map.


10

Alexis zaten Java 8'de yöntemi kullanarak bir cevap gönderdi toMap(keyMapper, valueMapper). Gereğince doc bu yöntem uygulanması için:

Döndürülen Haritanın türü, değiştirilebilirliği, serileştirilebilirliği veya iş parçacığı güvenliği konusunda hiçbir garanti yoktur.

Bu nedenle, Mapörneğin arayüzün belirli bir uygulamasına ilgi duymamız durumunda HashMap, aşırı yüklenmiş formu şu şekilde kullanabiliriz:

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

Her ne kadar ya Function.identity()da kullanmak i->iiyi olsa da , Function.identity()bunun yerine i -> ibu ilgili cevaba göre biraz bellek tasarrufu sağlayabilir .


1
2019'da büyük miktarda insanın hala gerçek Harita uygulamasını bilmediklerini fark etmedikleri komik gerçeği, lambda ile aldılar! Aslında bu, üretimde kullanacağım Java 8 lambdas ile bulduğum sadece bir cevap.
Pavlo

Bir Harita türü belirleyerek ancak birleştirme işlevi kullanmadan toplamanın bir yolu var mı?
Rosberg Linhares


5

Evrensel yöntem

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

Bu nasıl kullanılır? Ben ne geçmelidir converteryönteminde parametre?
IgorGanapolsky

4

Java-8 olmadan, bunu tek satır Commons koleksiyonlarında ve Closure sınıfında yapabilirsiniz

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

Neye ulaşmak istediğinize bağlı olarak birçok çözüm akla geliyor:

Her Liste öğesi anahtar ve değerdir

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

Liste öğelerinin onları arayacak bir şeyleri vardır, belki bir adı:

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

Liste öğelerinin onları arayacak bir şeyleri vardır ve benzersiz olduklarının garantisi yoktur: Googles MultiMaps'i kullanın

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

Tüm öğelere konumu bir anahtar olarak vermek:

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

...

Gerçekten ne elde etmek istediğinize bağlıdır.

Örneklerden de görebileceğiniz gibi, Harita bir anahtardan bir değere eşleme olurken, liste her biri yalnızca bir konuma sahip bir dizi öğedir. Yani otomatik olarak dönüştürülemezler.


Ancak Liste Öğesi konumunu anahtar olarak düşünebilir ve değerlerini haritaya koyabiliriz, bu iyi bir çözüm müdür?
Rachel

AFAIK evet! JDK'da bunu otomatik olarak yapan bir işlev yoktur, bu yüzden kendi rolünüzü döndürmeniz gerekir.
Daniel

java 8 akışıyla son sürümü (eşleme dizini harita anahtarı olarak kullanarak) yapmak mümkün müdür?
phil294

2

İşte tam olarak bu amaçla yazdığım küçük bir yöntem. Apache Commons'tan Doğrula'yı kullanır.

Kullanmaktan çekinmeyin.

/**
 * 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

Java 8'in akış API'sinden yararlanabilirsiniz.

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

Daha fazla ayrıntı için şu adresi ziyaret edin: http://codecramp.com/java-8-streams-api-convert-list-map/


1

List<?>Nesneleri a'ya dönüştürmek için kullanılan Java 8 örneği 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()));

Kod kopyalandı:
https://www.mkyong.com/java8/java-8-convert-list-to-map/


1

daha önce de belirtildiği gibi, java-8'de Koleksiyonerler tarafından özlü bir çözüm var:

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

ve ayrıca ikinci parametre olarak başka bir groupingBy yöntemini geçen birden çok grubu iç içe yerleştirebilirsiniz:

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

Bu şekilde, aşağıdaki gibi çok seviyeli bir haritaya sahip olacağız: Map<key, Map<key, List<Item>>>


1

Java-8 akışlarını kullanma

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

0

Kango_V'nin cevabını seviyorum, ama bence çok karmaşık. Bence bu daha basit - belki de çok basit. Eğimli ise, String'i Genel bir işaretleyici ile değiştirebilir ve herhangi bir Anahtar türü için çalıştırabilirsiniz.

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

Bunun gibi kullanılır:

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

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

0

Apache Commons MapUtils.populateMap

Java 8 kullanmıyorsanız ve herhangi bir nedenle açık bir döngü kullanmak istemiyorsanız MapUtils.populateMapApache Commons'tan deneyin .

MapUtils.populateMap

Diyelim bir Pairs listeniz var .

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

Ve şimdi nesnenin Pairanahtarının Haritasını istiyorsunuz Pair.

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

çıktı verir:

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

Bununla birlikte, bir fordöngüyü anlamak daha kolay olabilir. (Aşağıdaki aynı çıktıyı verir):

Map<String, Pair<String, String>> map = new HashMap<>();
for (Pair<String, String> pair : pairs) {
  map.put(pair.getKey(), pair);
}
System.out.println(map);
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.