2010-11-09 35 views
135

Gần đây tôi có cuộc trò chuyện với một đồng nghiệp về cách tối ưu để chuyển đổi List thành Map bằng Java và nếu có bất kỳ lợi ích cụ thể nào.Java: Cách chuyển đổi Danh sách thành Bản đồ

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.

Là cách tiếp cận tốt này:

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 nhất là gì? Tối ưu hóa được thực hiện với một số tham số (tốc độ/bộ nhớ) nhất định. –

+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. Do đó, không rõ chính xác bạn sẽ chuyển đổi từ Danh sách thành Bản đồ và quay lại như thế nào. –

+0

@Daniel: Bởi 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 của tất cả các cách và vì vậy nó sẽ là tốt để xem một số cách khác nhau của chuyển đổi danh sách để bản đồ. – Rachel

Trả lời

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

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

+1

Bạn cũng có thể khóa vào vị trí trong danh sách. – Jeremy

+0

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

+0

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

2

Nhiều giải pháp đến tâm trí, tùy thuộc vào những gì bạn muốn đạt được:

Mỗi List item là chìa khóa và giá trị so

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

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

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

Danh sách các yếu tố có nội dung cần xem xét và không đả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); 
} 

Cho tất cả các yếu tố vị trí như một chìa 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 loạt các yếu tố có vị trí. Vì vậy, họ chỉ đơn giản là không tự động chuyển đổi.

+0

Nhưng chúng ta có thể xem xét vị trí phần tử danh sách là chìa khóa và đặt giá trị của chúng trong bản đồ, đây có phải là giải pháp tốt không? – Rachel

+0

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

+0

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 java 8 luồng không? – Blauhirn

9

A ListMap khác nhau về mặt khái niệm. A List là bộ sưu tập các mặt hàng được sắp xếp. Các mục có thể chứa các bản sao 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 Map có 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 các mặt hàng của List của bạn, có thể hoặc không thể chuyển đổi thành số Map. Các mặt hàng của bạn có List không có bản sao không? Mỗi mục có một khóa duy nhất không? Nếu có thì có thể đặt chúng trong một số Map.

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 Validate 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; 
} 
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ĩ điều này đơn giản hơn - có lẽ quá đơn giản. Nếu nghiêng, bạn có thể thay thế chuỗi bằng dấu chung, và làm cho nó hoạt động đối với bất kỳ loại khóa nào.

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

phương pháp phổ

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

Cách sử dụng? Tôi nên thông qua tham số 'converter' trong phương thức nào? –

108

Chỉ trong trường hợp câu hỏi này không đóng cửa như là một bản sao, the right answer is to use Google Collections:

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

Điều này phải ở trên cùng –

+5

"* [Ổi] (https://code.google.com/p/guava-libraries/#Important_Warnings) chứa bộ siêu phù hợp nghiêm ngặt ** 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 cập nhật. – Tiny

+2

Việc sử dụng thư viện bên ngoài cho thao tác đơn giản như vậy là quá mức cần thiết. Đó là dấu hiệu của một thư viện 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 buồn khi java không có các phương thức hữu ích như "map" và "reduce" nhưng không hoàn toàn cần thiết. – linuxdan

176

Với , bạn sẽ có thể làm điều này trong một dòng sử dụng streams, và lớp Collectors.

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

Output:

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

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

Và lưu ý đầy đủ rằng bạn có thể sử dụng toán tử nhị phân nếu hàm của bạn không phải là tính từ. Ví dụ chúng ta hãy xem xét các chức năng này List lập bản đồ đó cho một giá trị int, tính toán kết quả của nó 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ẽ nhận được một lỗi nói java.lang.IllegalStateException: Duplicate key 1. Điều này là do 1% 3 là giống như 4% 3 và do đó có cùng một giá trị quan trọng cho chức năng ánh xạ khóa. Trong trường hợp này, bạn có thể cung cấp toán tử hợp nhất.

Dưới đây là tổng hợp các giá trị; (i1, i2) -> i1 + i2; có thể 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ờ kết quả đầu ra:

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

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

+8

sử dụng tốt hơn 'Function.identity()' thay vì 'item -> item' –

+0

@EmmanuelTouzery Well,' Function.identity() 'trả về' t -> t; '. –

+0

Chắc chắn, cả hai đều hoạt động. Tôi đoán đó là vấn đề về hương vị. Tôi tìm thấy Function.identity() ngay lập tức nhận ra được. –

11

Vì Java 8, answer by @ZouZou sử dụng trình thu thập Collectors.toMap 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 tôi 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ớp 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 một List<Student>: lớp

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

Để thảo luận về các thông 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] (http://stackoverflow.com/q/26691278). – glts

+0

Điều này sẽ ném một ngoại lệ trong trường hợp, có các phím trùng lặp. Giống như: Ngoại lệ trong chuỗi "chính" java.lang.IllegalStateException: Khóa trùng lặp .... Để biết chi tiết, hãy xem: http://codecramp.com/java-8-streams-api-convert-list-map/ – EMM

+0

@ EMM Tất nhiên, như dự định và tài liệu trong Javadoc. – glts

3

Nếu không có java-8, bạn sẽ có thể làm điều này trong một dòng Commons bộ sưu tập, và Đó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

Bạn có thể tận dụng các dòng API 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 chuyến thăm: http://codecramp.com/java-8-streams-api-convert-list-map/

1

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

Có gì đảm bảo vào loại, mutability, serializability, hoặc thread-an toàn của bản đồ trở lại.

Vì vậy, trong trường hợp chúng tôi quan tâm đến việc triển khai cụ thể giao diện Map ví dụ: HashMap sau đó chúng ta có thể sử dụng 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->i là tốt nhưng có vẻ như Function.identity() thay vì i -> i có thể tiết kiệm một số bộ nhớ theo liên quan answer này.

0

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 thể được bất kỳ đối tượng bạn sử dụng.

Các vấn đề liên quan