Java: Cách chuyển đổi Danh sách thành Bản đồ


224

Gần đây tôi có cuộc trò chuyện với một đồng nghiệp về cách tốt nhất để chuyển đổi Listsang MapJava và nếu có bất kỳ lợi ích cụ thể nào khi làm như vậy.

Tôi muốn biết phương pháp chuyển đổi tối ưu và sẽ thực sự đánh giá cao nếu có ai có thể hướng dẫn tôi.

Đây có phải là cách tiếp cận tốt:

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

2
Cách tối ưu tốt nhất là gì? Tối ưu hóa được thực hiện với một số tham số nhất định (tốc độ / bộ nhớ).
Daniel Fath

6
Danh sách khác với Bản đồ theo cách khái niệm - Bản đồ có khái niệm về cặp 'khóa, giá trị', trong khi Danh sách thì không. Vì điều này không rõ chính xác bạn sẽ chuyển đổi từ Danh sách sang Bản đồ như thế nào và trở lại.
Victor Sorokin

1
@Daniel: Theo Tối ưu, tôi có nghĩa là cách tốt nhất để làm như vậy trong số tất cả các cách khác nhau giữa không chắc chắn về tất cả các cách và vì vậy sẽ rất tốt khi thấy một số cách khác nhau để chuyển đổi danh sách sang bản đồ.
Rachel


Câu trả lời:


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

Tất nhiên, giả sử rằng mỗi Mục có một getKey()phương thức trả về khóa của loại thích hợp.


1
Bạn cũng có thể nhấn vào vị trí trong danh sách.
Jeremy

@Jim: Tôi có cần đặt getKey()tham số cụ thể nào không?
Rachel

Ngoài ra, giá trị trong Bản đồ là gì, bạn có thể giải thích bằng một ví dụ không?
Rachel

1
@Rachel - Giá trị là mục trong danh sách và khóa là thứ làm cho mục đó là duy nhất, được xác định bởi bạn. Việc sử dụng của Jim getKey()là tùy tiện.
Jeremy

1
Bạn biết kích thước trước để bạn có thể thực hiện Map <Key, Item> map = new HashMap <Key, Item> (list.size ());
Víctor Romero

316

Với , bạn sẽ có thể thực hiện việc này trong một dòng bằng cách sử dụng luồngCollectorslớp.

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

Bản demo ngắn:

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

Đầu ra:

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

Như đã lưu ý trong các bình luận, bạn có thể sử dụng Function.identity()thay vì item -> item, mặc dù tôi thấy i -> ikhá rõ ràng.

Và để được lưu ý đầy đủ rằng bạn có thể sử dụng toán tử nhị phân nếu chức năng của bạn không phải là tính từ. Ví dụ, hãy xem xét điều này Listvà hàm ánh xạ cho một giá trị int, tính kết quả của nó theo 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));

Khi chạy mã này, bạn sẽ gặp lỗi java.lang.IllegalStateException: Duplicate key 1. Điều này là do 1% 3 giống với 4% 3 và do đó có cùng giá trị khóa được cung cấp cho hàm ánh xạ khóa. Trong trường hợp này, bạn có thể cung cấp một toán tử hợp nhất.

Đây là một tổng hợp các giá trị; (i1, i2) -> i1 + i2;có thể được thay thế bằng tham chiếu phương thức Integer::sum.

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

mà bây giờ đầu ra:

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

Hy vọng nó giúp! :)


19
sử dụng tốt hơn Function.identity()thay vìitem -> item
Emmanuel Touzery 04/11 '

1
@EmmanuelTouzery Vâng, Function.identity()trả lại t -> t;.
Alexis C.

2
Chắc chắn, cả hai đều làm việc. Tôi đoán đó là vấn đề của hương vị. Tôi thấy Function.identity () dễ nhận biết hơn ngay lập tức.
Emmanuel Touzery 15/2/2015

OP không xử lý bất kỳ pojos nào, chỉ có chuỗi và số nguyên mà bạn không thể cal ::getKey.
phil294

@Blauhirn Tôi biết, ví dụ của tôi dựa trên lớp tùy chỉnh ngay bên dưới. Bạn có thể tự do sử dụng bất kỳ chức năng nào để tạo khóa từ các giá trị.
Alexis C.

115

Chỉ trong trường hợp câu hỏi này không bị đóng như một bản sao, câu trả lời đúng là sử dụng Bộ sưu tập của Google :

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

17
Điều này nên ở trên cùng
Martin Andersson

6
" Quả ổi chứa một siêu bộ tương thích nghiêm ngặt của Thư viện Bộ sưu tập Google cũ, không dùng nữa . Bạn không nên sử dụng thư viện đó nữa. " Có thể cần một bản cập nhật.
Tiny

3
Sử dụng một thư viện bên ngoài cho một hoạt động đơn giản như vậy là quá mức cần thiết. Đó hoặc là một dấu hiệu của một thư viện tiêu chuẩn rất yếu. Trong trường hợp này, câu trả lời của @ jim-garrison là hoàn toàn hợp lý. Thật đáng buồn khi java không có các phương thức hữu ích như "bản đồ" và "thu nhỏ" nhưng không hoàn toàn cần thiết.
linuxdan

2
Cái này dùng ổi. Thật không may, Guava siêu chậm trên Android, vì vậy giải pháp này không nên được sử dụng trong một dự án Android.
IgorGanapolsky

nếu mục trong danh sách có các vai trò trùng lặp, đoạn mã trên sẽ ném ngoại lệ,
Junchen Liu

17

Sử dụng Java 8 bạn có thể làm như sau:

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

Value có thể là bất kỳ đối tượng bạn sử dụng.


16

Kể từ Java 8, câu trả lời của @ZouZou bằng cách sử dụng trình Collectors.toMapthu thập chắc chắn là cách thành ngữ để giải quyết vấn đề này.

Và vì đây là một nhiệm vụ phổ biến, chúng ta có thể biến nó thành một tiện ích tĩnh.

Bằng cách đó, giải pháp thực sự trở thành một lót.

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

Và đây là cách bạn sẽ sử dụng nó trên List<Student>:

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

Để thảo luận về các tham số loại của phương pháp này, hãy xem câu hỏi tiếp theo của tôi .
chiếu vào

Điều này sẽ ném một ngoại lệ trong trường hợp, có các khóa trùng lặp. Sth like: Exception in thread "main" java.lang.IllegalStateException: Khóa trùng lặp .... Để biết chi tiết, hãy xem: codecramp.com/java-8-streams-api-convert-list-map
EMM

@EMM Tất nhiên, như dự định và được ghi lại trong Javadoc.
chiếu

Có, cập nhật câu trả lời cho trường hợp trùng lặp. Vui lòng xem lại. Cảm ơn
EMM

10

A ListMaplà khái niệm khác nhau. A Listlà một bộ sưu tập các mặt hàng. Các mục có thể chứa các mục trùng lặp và một mục có thể không có bất kỳ khái niệm nào về mã định danh duy nhất (khóa). A Mapcó các giá trị được ánh xạ tới các khóa. Mỗi khóa chỉ có thể trỏ đến một giá trị.

Do đó, tùy thuộc vào Listcác mặt hàng của bạn , có thể hoặc không thể chuyển đổi nó thành a Map. Các Listmặt hàng của bạn không có bản sao? Mỗi mục có một khóa duy nhất? Nếu như vậy thì nó có thể đặt chúng trong một Map.


10

Alexis đã đăng một câu trả lời trong Java 8 bằng phương thức toMap(keyMapper, valueMapper). Theo tài liệu để thực hiện phương pháp này:

Không có sự đảm bảo nào về loại, tính dễ biến đổi, tính tuần tự hoặc tính an toàn của luồng của Bản đồ được trả về.

Vì vậy, trong trường hợp chúng ta quan tâm đến việc triển khai Mapgiao diện cụ thể, ví dụ như HashMapsau đó chúng ta có thể sử dụng biểu mẫu quá tải như:

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

Mặc dù sử dụng một trong hai Function.identity()hoặc i->itốt nhưng có vẻ như Function.identity()thay vì i -> icó thể tiết kiệm một số bộ nhớ theo câu trả lời liên quan này .


1
Một sự thật buồn cười là vào năm 2019, một lượng lớn người vẫn không nhận ra rằng họ không biết triển khai Bản đồ thực sự mà họ có được với lambda! Trên thực tế đây chỉ là một câu trả lời tôi tìm thấy với Java 8 lambdas mà tôi sẽ sử dụng trong sản xuất.
Pavlo

Có cách nào để thu thập bằng cách chỉ định loại Bản đồ nhưng không sử dụng chức năng hợp nhất không?
Rosberg Linhares


5

Phương pháp phổ quát

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

Làm thế nào để sử dụng này? Tôi nên vượt qua như là convertertham số trong phương thức?
IgorGanapolsky

4

Nếu không có java-8, bạn sẽ có thể thực hiện điều này trong các bộ sưu tập Commons một dòng và lớp Đóng

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

Nhiều giải pháp xuất hiện trong đầu, tùy thuộc vào những gì bạn muốn đạt được:

Mỗi mục Danh sách là khóa và giá trị

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

Danh sách các yếu tố có một cái gì đó để tìm kiếm chúng, có thể là một tên:

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

Các phần tử danh sách có thứ gì đó để tìm kiếm chúng và không có gì đảm bảo rằng chúng là duy nhất: Sử dụng Googles MultiMaps

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

Đưa tất cả các yếu tố vào vị trí như một khóa:

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

...

Nó thực sự phụ thuộc vào những gì bạn muốn đạt được.

Như bạn có thể thấy từ các ví dụ, Bản đồ là ánh xạ từ khóa đến giá trị, trong khi danh sách chỉ là một chuỗi các yếu tố có vị trí. Vì vậy, họ chỉ đơn giản là không tự động chuyển đổi.


Nhưng chúng ta có thể coi vị trí List Element là chìa khóa và đưa giá trị của chúng vào bản đồ, đây có phải là một giải pháp tốt?
Rachel

AFAIK có! Không có chức năng nào trong JDK thực hiện điều đó một cách tự động, vì vậy bạn phải tự cuộn.
Daniel

có thể thực hiện phiên bản cuối cùng (sử dụng chỉ mục mảng làm khóa bản đồ) với các luồng java 8 không?
phil294

2

Đây là một phương pháp nhỏ tôi đã viết cho chính xác mục đích này. Nó sử dụng Xác thực từ Apache Commons.

Hãy sử dụng nó.

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

Bạn có thể tận dụng API luồng của 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;
  }
}

Để biết thêm chi tiết, hãy truy cập: http://codecramp.com/java-8-streams-api-convert-list-map/


1

Một ví dụ Java 8 để chuyển đổi một List<?>đối tượng thành 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()));

Mã được sao chép từ:
https://www.mkyong.com/java8/java-8-convert-list-to-map/


1

như đã nói, trong java-8, chúng ta có giải pháp ngắn gọn của Collector:

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

và ngoài ra, bạn có thể lồng nhiều nhóm thông qua một phương thức groupingBy khác làm tham số thứ hai:

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

Theo cách này, chúng tôi sẽ có bản đồ đa cấp, như thế này: Map<key, Map<key, List<Item>>>


1

Sử dụng luồng java-8

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

0

Tôi thích câu trả lời của Kango_V, nhưng tôi nghĩ nó quá phức tạp. Tôi nghĩ rằng điều này là đơn giản hơn - có thể quá đơn giản. Nếu nghiêng, bạn có thể thay thế Chuỗi bằng Dấu đánh dấu Chung và làm cho nó hoạt động với mọi loại Khóa.

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

Được sử dụng như thế này:

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

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

0

Bản đồ chung của Apache CommUtils.populationMap

Nếu bạn không sử dụng Java 8 và bạn không muốn sử dụng một vòng lặp rõ ràng vì một số lý do, hãy thử MapUtils.populateMaptừ Apache Commons.

MapUtils.populationMap

Nói rằng bạn có một danh sách của Pairs.

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

Và bây giờ bạn muốn có một Bản đồ về Pairkhóa của Pairđối tượng.

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

cho đầu ra:

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

Điều đó đang được nói, một forvòng lặp có thể dễ hiểu hơn. (Điều này dưới đây cho cùng một đầu ra):

Map<String, Pair<String, String>> map = new HashMap<>();
for (Pair<String, String> pair : pairs) {
  map.put(pair.getKey(), pair);
}
System.out.println(map);
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.