Java: วิธีการแปลง List เป็น Map


224

เมื่อเร็ว ๆ นี้ฉันได้พูดคุยกับเพื่อนร่วมงานเกี่ยวกับสิ่งที่จะเป็นวิธีที่ดีที่สุดในการแปลงListเป็นMapJava และหากมีประโยชน์เฉพาะในการทำเช่นนั้น

ฉันต้องการทราบวิธีการแปลงที่เหมาะสมที่สุดและจะขอบคุณถ้ามีใครสามารถแนะนำฉันได้

นี่เป็นวิธีการที่ดีหรือไม่:

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

2
วิธีที่ดีที่สุดที่ดีที่สุดคืออะไร? การปรับให้เหมาะสมจะทำโดยคำนึงถึงพารามิเตอร์ (ความเร็ว / หน่วยความจำ)
Daniel Fath

6
รายการแตกต่างจากแผนที่ในแนวความคิด - แผนที่มีความคิดเกี่ยวกับคู่ของ 'คีย์, ค่า' ในขณะที่รายการไม่มี รับนี้มันชัดเจนว่าคุณจะแปลงจากรายการเป็นแผนที่และกลับ
Victor Sorokin

1
@Daniel: โดยดีที่สุดฉันหมายถึงสิ่งที่เป็นวิธีที่ดีที่สุดในการทำระหว่างวิธีที่แตกต่างกันทั้งหมดระหว่างฉันไม่แน่ใจทุกวิธีและมันจะเป็นการดีที่ได้เห็นวิธีการแปลงรายการเป็นแผนที่
Rachel

ซ้ำกันเป็นไปได้ของJava: วิธีการแปลง List <?> เป็น Map <String,?>
ripper234

คำตอบ:


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

สมมติว่าแต่ละรายการมีgetKey()เมธอดที่ส่งคืนคีย์ของชนิดที่เหมาะสม


1
คุณสามารถป้อนตำแหน่งในรายการ
Jeremy

@Jim: ฉันจำเป็นต้องตั้งค่าgetKey()พารามิเตอร์เฉพาะหรือไม่?
Rachel

นอกจากนี้สิ่งที่จะเป็นค่าในแผนที่คุณสามารถอธิบายตัวอย่างได้ไหม?
Rachel

1
@Rachel - ค่าเป็นรายการในรายการและคีย์เป็นสิ่งที่ทำให้รายการไม่ซ้ำกันซึ่งคุณเป็นผู้กำหนด การใช้งานของจิมgetKey()นั้นเป็นสิ่งที่ไม่มีเหตุผล
Jeremy

1
คุณทราบขนาดไว้ล่วงหน้าเพื่อให้คุณสามารถทำแผนที่ <คีย์รายการ> แผนที่ = ใหม่ HashMap <คีย์รายการ> (list.size ());
Víctor Romero

316

กับ คุณจะสามารถทำได้ในหนึ่งบรรทัดโดยใช้สตรีมและCollectorsคลาส

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

การสาธิตสั้น ๆ :

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

เอาท์พุท:

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

ตามที่ระบุไว้ในความคิดเห็นคุณสามารถใช้Function.identity()แทนitem -> itemแม้ว่าฉันจะพบi -> iค่อนข้างชัดเจน

และเพื่อให้ทราบว่าคุณสามารถใช้ตัวดำเนินการไบนารี่ได้หากฟังก์ชั่นของคุณไม่ได้เป็น bijective ตัวอย่างเช่นลองพิจารณาฟังก์ชันนี้Listและฟังก์ชันการแมปที่มีค่า int คำนวณผลลัพธ์ของมัน 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));

เมื่อเรียกใช้รหัสนี้คุณจะพบข้อผิดพลาด java.lang.IllegalStateException: Duplicate key 1เมื่อใช้รหัสนี้คุณจะได้รับข้อผิดพลาดว่านี่เป็นเพราะ 1% 3 เป็นเช่นเดียวกับ 4% 3 และด้วยเหตุนี้จึงมีค่าคีย์เดียวกันที่กำหนดให้กับฟังก์ชั่นการทำแผนที่ที่สำคัญ ในกรณีนี้คุณสามารถให้บริการตัวดำเนินการผสาน

นี่คือสิ่งที่รวมค่า; ที่สามารถแทนที่ด้วยวิธีการอ้างอิง(i1, i2) -> i1 + i2;Integer::sum

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

ซึ่งตอนนี้ผลลัพธ์:

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

หวังว่ามันจะช่วย! :)


19
ใช้ดีกว่าFunction.identity()แทนitem -> item
Emmanuel Touzery

1
@EmmanuelTouzery เอ่อFunction.identity()กลับมาt -> t;แล้ว
Alexis C.

2
แน่นอนว่าใช้ได้ทั้งคู่ ฉันคิดว่ามันเป็นเรื่องของรสนิยม ฉันพบ Function.identity () รู้จักได้มากขึ้นในทันที
Emmanuel Touzery

OP ไม่ได้จัดการ POJOs ใด ๆ ::getKeyเพียงแค่สตริงและจำนวนเต็มกับคนที่คุณไม่สามารถตกลูก
phil294

@Blauhirn ฉันรู้ว่าตัวอย่างของฉันขึ้นอยู่กับชั้นเรียนที่กำหนดเองด้านล่าง คุณสามารถใช้ฟังก์ชันใดก็ได้เพื่อสร้างคีย์ของคุณจากค่าต่างๆ
Alexis C.

115

ในกรณีที่คำถามนี้ไม่ได้ปิดเหมือนคำตอบที่ถูกต้องคือใช้ 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
นี่ควรจะอยู่ด้านบน
Martin Andersson

6
" Guavaมี superset ที่เข้ากันได้อย่างเข้มงวดกับห้องสมุด Google Collections เก่าที่เลิกใช้แล้วคุณไม่ควรใช้ห้องสมุดนั้นอีกต่อไป " อาจจำเป็นต้องมีการอัปเดต
Tiny

3
การใช้ไลบรารี่ภายนอกสำหรับการดำเนินการอย่างง่ายนั้นเกินความจำเป็น นั่นเป็นสัญญาณของห้องสมุดมาตรฐานที่อ่อนแอมาก ในกรณีนี้คำตอบของ @jim-garrison นั้นสมเหตุสมผลอย่างสมบูรณ์ เป็นเรื่องน่าเศร้าที่ java ไม่มีวิธีการที่เป็นประโยชน์เช่น "แผนที่" และ "ลด" แต่ไม่จำเป็นทั้งหมด
linuxdan

2
สิ่งนี้ใช้ฝรั่ง น่าเสียดายที่ Guava นั้นช้าสุด ๆ ใน Android ดังนั้นโซลูชันนี้ไม่ควรใช้ในโครงการ Android
IgorGanapolsky

หากรายการในรายการซ้ำบทบาทชื่อรหัสด้านบนจะแสดงข้อผิดพลาด
Junchen Liu

17

การใช้ Java 8 คุณสามารถทำสิ่งต่อไปนี้:

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

Value สามารถเป็นวัตถุใด ๆ ที่คุณใช้


16

ตั้งแต่ Java 8 คำตอบโดย @ZouZouใช้Collectors.toMapสะสมนั้นเป็นวิธีการแก้ปัญหาที่แน่นอน

และเนื่องจากเป็นงานทั่วไปเราสามารถทำให้มันเป็นยูทิลิตี้คงที่

วิธีการแก้ปัญหาอย่างแท้จริงจะกลายเป็นหนึ่งซับ

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

และนี่คือวิธีที่คุณจะใช้กับList<Student>:

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

สำหรับการอภิปรายของพารามิเตอร์ประเภทของวิธีนี้ดูติดตามคำถามของฉัน
glts

สิ่งนี้จะทำให้เกิดข้อยกเว้นในกรณีที่มีกุญแจที่ซ้ำกัน Sth like: ข้อยกเว้นในเธรด "main" java.lang.IllegalStateException: คีย์ซ้ำ .... สำหรับรายละเอียดโปรดดู: codecramp.com/java-8-streams-api-convert-list-map
EMM

@EMM แน่นอนตามที่ตั้งใจและบันทึกไว้ใน Javadoc
glts

ใช่อัปเดตคำตอบสำหรับครอบคลุมกรณีที่ซ้ำกัน กรุณาตรวจสอบ ขอบคุณ
EMM

10

A ListและMapมีแนวคิดแตกต่างกัน A Listเป็นการรวบรวมรายการ รายการสามารถมีรายการที่ซ้ำกันและรายการอาจไม่มีแนวคิดของตัวระบุที่ไม่ซ้ำกัน (คีย์) Mapมีค่าที่แม็พกับคีย์ แต่ละคีย์สามารถชี้ไปที่ค่าเดียวเท่านั้น

ดังนั้นขึ้นอยู่กับรายการของมันอาจจะหรืออาจจะไม่เป็นไปได้ที่จะแปลงเป็นList MapรายการของคุณListไม่มีรายการซ้ำหรือไม่ แต่ละรายการมีรหัสที่ไม่ซ้ำกันหรือไม่ Mapถ้าเป็นอย่างนั้นมันเป็นไปได้ที่จะใส่ไว้ใน


10

Alexis ได้โพสต์คำตอบในJava 8โดยใช้วิธีการtoMap(keyMapper, valueMapper)แล้ว ตามเอกสารสำหรับการใช้วิธีการนี้:

ไม่มีการรับประกันเกี่ยวกับประเภทความไม่แน่นอนความต่อเนื่องของอนุกรมหรือความปลอดภัยของเธรดของแผนที่ที่ส่งคืน

ดังนั้นในกรณีที่เราสนใจในการติดตั้งMapส่วนต่อประสานที่เฉพาะเจาะจงเช่นHashMapจากนั้นเราสามารถใช้แบบฟอร์มที่มีการโอเวอร์โหลดเป็น:

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

แม้ว่าใช้อย่างใดอย่างหนึ่งFunction.identity()หรือใช้ได้i->iแต่ดูเหมือนว่าFunction.identity()แทนที่จะi -> iบันทึกหน่วยความจำบางส่วนตามคำตอบที่เกี่ยวข้องนี้


1
ข้อเท็จจริงที่ตลกที่ในปี 2019 ผู้คนจำนวนมากยังไม่ทราบว่าพวกเขาไม่รู้การใช้แผนที่จริงที่พวกเขาได้รับด้วยแลมบ์ดา! ที่จริงนี่เป็นเพียงหนึ่งคำตอบที่ฉันพบกับ lambdas Java 8 ที่ฉันจะใช้ในการผลิต
Pavlo

มีวิธีในการรวบรวมโดยการระบุประเภทแผนที่ แต่ไม่ได้ใช้ฟังก์ชั่นผสานหรือไม่?
Rosberg Linhares


5

วิธีการสากล

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

ใช้อย่างไร ฉันควรผ่านอะไรเป็นconverterพารามิเตอร์ในวิธีการ?
IgorGanapolsky

4

หากไม่มี java-8 คุณจะสามารถทำสิ่งนี้ได้ในคอลเล็กชันคอมมอนส์บรรทัดเดียวและคลาสปิด

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

การแก้ปัญหาหลายอย่างนั้นขึ้นอยู่กับว่าคุณต้องการประสบความสำเร็จมากแค่ไหน:

ทุกรายการเป็นกุญแจสำคัญและความคุ้มค่า

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

อิลิเมนต์รายการมีบางสิ่งที่จะค้นหาพวกเขาอาจเป็นชื่อ:

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

องค์ประกอบรายการมีบางสิ่งที่สามารถค้นหาได้และไม่รับประกันว่าจะไม่ซ้ำใคร: ใช้ Googles MultiMaps

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

ให้องค์ประกอบทั้งหมดอยู่ในตำแหน่งสำคัญ:

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

...

มันขึ้นอยู่กับสิ่งที่คุณต้องการบรรลุ

อย่างที่คุณเห็นจากตัวอย่างแผนที่คือการแมปจากคีย์ไปยังค่าในขณะที่รายการเป็นเพียงชุดขององค์ประกอบที่มีตำแหน่งแต่ละรายการ ดังนั้นมันจึงไม่แปลงสภาพโดยอัตโนมัติ


แต่เราสามารถพิจารณาตำแหน่งองค์ประกอบของรายการเป็นคีย์และใส่ค่าลงในแผนที่นี่เป็นทางออกที่ดีหรือไม่
Rachel

AFAIK ใช่แล้ว! ไม่มีฟังก์ชั่นใน JDK ที่ทำเช่นนั้นโดยอัตโนมัติดังนั้นคุณต้องม้วนตัวเอง
แดเนียล

เป็นไปได้ไหมที่จะทำรุ่นสุดท้าย (โดยใช้ดัชนีอาร์เรย์เป็นรหัสแผนที่) กับ java 8 stream?
phil294

2

นี่เป็นวิธีการเล็กน้อยที่ฉันเขียนเพื่อจุดประสงค์นี้ มันใช้การตรวจสอบจาก Apache Commons

รู้สึกอิสระที่จะใช้มัน

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

คุณสามารถใช้ประโยชน์จากสตรีม API ของ 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;
  }
}

สำหรับรายละเอียดเพิ่มเติมเยี่ยมชม: http://codecramp.com/java-8-streams-api-convert-list-map/


1

ตัวอย่าง Java 8 เพื่อแปลงList<?>วัตถุเป็น a 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()));

คัดลอกรหัสจาก:
https://www.mkyong.com/java8/java-8-convert-list-to-map/


1

ดังที่ได้กล่าวไปแล้วใน java-8 เรามีวิธีแก้ปัญหาโดยนักสะสม:

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

และคุณสามารถซ้อนหลายกลุ่มผ่านเมธอด groupingBy อื่น ๆ เป็นพารามิเตอร์ที่สอง:

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

ด้วยวิธีนี้เราจะมีแผนที่หลายระดับดังนี้: Map<key, Map<key, List<Item>>>



0

ฉันชอบคำตอบของ Kango_V แต่ฉันคิดว่ามันซับซ้อนเกินไป ฉันคิดว่ามันง่ายกว่า - อาจจะง่ายเกินไป หากเอียงคุณสามารถแทนที่สตริงด้วยเครื่องหมายทั่วไปและทำให้มันใช้งานได้กับคีย์ประเภทใดก็ได้

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

ใช้แบบนี้:

        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 และคุณไม่ต้องการใช้การวนซ้ำอย่างชัดเจนด้วยเหตุผลบางประการให้ลองMapUtils.populateMapจาก Apache Commons

MapUtils.populateMap

สมมติว่าคุณมีรายชื่อของPairs

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

และตอนนี้คุณต้องการแผนที่ของPairกุญแจไปยัง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);

ให้ผลลัพธ์:

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

ที่ถูกกล่าวว่าforวงอาจจะง่ายต่อการเข้าใจ (ด้านล่างนี้ให้ผลลัพธ์เดียวกัน):

Map<String, Pair<String, String>> map = new HashMap<>();
for (Pair<String, String> pair : pairs) {
  map.put(pair.getKey(), pair);
}
System.out.println(map);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.