Danh sách Java 8 <V> vào Bản đồ <K, V>


932

Tôi muốn dịch Danh sách các đối tượng thành Bản đồ bằng cách sử dụng các luồng và lambd của Java 8.

Đây là cách tôi sẽ viết nó trong Java 7 trở xuống.

private Map<String, Choice> nameMap(List<Choice> choices) {
        final Map<String, Choice> hashMap = new HashMap<>();
        for (final Choice choice : choices) {
            hashMap.put(choice.getName(), choice);
        }
        return hashMap;
}

Tôi có thể thực hiện điều này một cách dễ dàng bằng cách sử dụng Java 8 và ổi nhưng tôi muốn biết làm thế nào để làm điều này mà không có ổi.

Ở ổi:

private Map<String, Choice> nameMap(List<Choice> choices) {
    return Maps.uniqueIndex(choices, new Function<Choice, String>() {

        @Override
        public String apply(final Choice input) {
            return input.getName();
        }
    });
}

Và ổi với Java 8 lambdas.

private Map<String, Choice> nameMap(List<Choice> choices) {
    return Maps.uniqueIndex(choices, Choice::getName);
}

Câu trả lời:


1394

Dựa trên Collectorstài liệu, nó đơn giản như:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName,
                                              Function.identity()));

167
Như một lưu ý phụ, ngay cả sau Java 8, JDK vẫn không thể cạnh tranh trong một cuộc thi ngắn gọn. Sự thay thế Guava trông rất dễ đọc : Maps.uniqueIndex(choices, Choice::getName).
Bogdan Calmac 3/03/2015

3
Sử dụng (được nhập tĩnh) Seqtừ thư viện JOOL (mà tôi muốn giới thiệu cho bất kỳ ai sử dụng Java 8), bạn cũng có thể cải thiện tính ngắn gọn với: seq(choices).toMap(Choice::getName)
lukens 18/03/2017

5
Có bất kỳ lợi ích nào từ việc sử dụng Function.identity không? Ý tôi là, nó -> nó ngắn hơn
shabunc

9
@shabunc Tôi không biết bất kỳ lợi ích nào và thực sự sử dụng it -> itbản thân mình. Function.identity()được sử dụng ở đây chủ yếu vì nó được sử dụng trong tài liệu tham khảo và đó là tất cả những gì tôi biết về lambdas tại thời điểm viết bài
zapl

12
@zapl, oh, thực ra hóa ra có những lý do đằng sau điều này - stackoverflow.com/questions/28032827/ ích
shabunc

307

Nếu khóa của bạn KHÔNG được đảm bảo là duy nhất cho tất cả các thành phần trong danh sách, bạn nên chuyển đổi nó thành một Map<String, List<Choice>>thay vìMap<String, Choice>

Map<String, List<Choice>> result =
 choices.stream().collect(Collectors.groupingBy(Choice::getName));

76
Điều này thực sự mang đến cho bạn Bản đồ <Chuỗi, Danh sách <Lựa chọn >> liên quan đến khả năng của các khóa không duy nhất, nhưng không phải là những gì OP yêu cầu. Trong Guava, Multimaps.index (sự lựa chọn, Sự lựa chọn :: getName) có lẽ là một lựa chọn tốt hơn nếu đây là điều bạn muốn dù sao đi nữa.
Richard Nichols

hay đúng hơn là sử dụng Multimap <String, Choice> của Guava, khá tiện dụng trong các tình huống có cùng khóa ánh xạ tới nhiều giá trị. Có nhiều phương thức tiện ích khác nhau có sẵn trong Guava để sử dụng các cấu trúc dữ liệu đó thay vì tạo Bản đồ <Chuỗi, Danh sách <Lựa chọn >>
Neeraj B.

1
@RichardNichols tại sao Multimapsphương pháp ổi là một lựa chọn tốt hơn? Nó có thể là một sự bất tiện vì nó không trả lại MapObject.
theyuv

156

Sử dụng getName()làm khóa và Choicechính nó là giá trị của bản đồ:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName, c -> c));

19
Hãy viết một số mô tả để người dùng có thể hiểu.
Mayank Jain

4
Nó thực sự quá tệ không có nhiều chi tiết ở đây, vì tôi thích câu trả lời này nhất.
MadConan

Collectors.toMap(Choice::getName,c->c)(2 ký tự ngắn hơn)
Ferrybig

7
Nó tương đương với choices.stream().collect(Collectors.toMap(choice -> choice.getName(),choice -> choice)); chức năng đầu tiên cho khóa, chức năng thứ hai cho giá trị
Waterscar

16
Tôi biết cách dễ dàng để xem và hiểu c -> cnhưng Function.identity()mang nhiều thông tin ngữ nghĩa hơn. Tôi thường sử dụng nhập tĩnh để tôi chỉ có thể sử dụngidentity()
Hank D

28

Đây là một cái khác trong trường hợp bạn không muốn sử dụng Collector.toMap ()

Map<String, Choice> result =
   choices.stream().collect(HashMap<String, Choice>::new, 
                           (m, c) -> m.put(c.getName(), c),
                           (m, u) -> {});

1
Cái nào tốt hơn để sử dụng sau đó Collectors.toMap()hoặc HashMap của chúng ta như bạn đã trình bày trong ví dụ trên?
Swapnil Gangrade

1
Ví dụ này cung cấp một ví dụ về cách đặt một cái gì đó khác trên bản đồ. Tôi muốn một giá trị không được cung cấp bởi một cuộc gọi phương thức. Cảm ơn!
th3morg

1
Hàm đối số thứ ba không đúng. Ở đó bạn nên cung cấp một số chức năng để hợp nhất hai Hashmap, một cái gì đó như Hashmap :: putAll
jesantana

25

Hầu hết các câu trả lời được liệt kê, bỏ lỡ một trường hợp khi danh sách có các mục trùng lặp . Trong trường hợp đó có câu trả lời sẽ ném IllegalStateException. Tham khảo mã dưới đây để xử lý trùng lặp danh sách :

public Map<String, Choice> convertListToMap(List<Choice> choices) {
    return choices.stream()
        .collect(Collectors.toMap(Choice::getName, choice -> choice,
            (oldValue, newValue) -> newValue));
  }

19

Thêm một lựa chọn theo cách đơn giản

Map<String,Choice> map = new HashMap<>();
choices.forEach(e->map.put(e.getName(),e));

3
Không có sự khác biệt khả thi trong việc sử dụng loại 7 hoặc java này.
Ashish Lohia

3
Tôi thấy điều này dễ đọc và hiểu những gì đang diễn ra hơn các cơ chế khác
Freiheit

2
SO đã hỏi với Java 8 Streams.
Mehraj Malik

18

Ví dụ: nếu bạn muốn chuyển đổi các trường đối tượng thành bản đồ:

Đối tượng ví dụ:

class Item{
        private String code;
        private String name;

        public Item(String code, String name) {
            this.code = code;
            this.name = name;
        }

        //getters and setters
    }

Và hoạt động chuyển đổi danh sách sang bản đồ:

List<Item> list = new ArrayList<>();
list.add(new Item("code1", "name1"));
list.add(new Item("code2", "name2"));

Map<String,String> map = list.stream()
     .collect(Collectors.toMap(Item::getCode, Item::getName));

12

Nếu bạn không phiền khi sử dụng các thư viện của bên thứ 3, lib của cyclop- Reac (tiết lộ tôi là cộng tác viên) có các phần mở rộng cho tất cả các loại Bộ sưu tập JDK , bao gồm Danh sáchBản đồ .

ListX<Choices> choices;
Map<String, Choice> map = choices.toMap(c-> c.getName(),c->c);

10

Tôi đã cố gắng thực hiện điều này và thấy rằng, bằng cách sử dụng các câu trả lời ở trên, khi sử dụng Functions.identity()khóa cho Bản đồ, sau đó tôi gặp vấn đề với việc sử dụng một phương thức cục bộ như this::localMethodNamethực sự hoạt động vì vấn đề gõ.

Functions.identity()thực sự làm một cái gì đó để gõ trong trường hợp này vì vậy phương thức sẽ chỉ hoạt động bằng cách trả về Objectvà chấp nhận một tham sốObject

Để giải quyết điều này, cuối cùng tôi đã bỏ qua Functions.identity()và sử dụng s->s.

Vì vậy, mã của tôi, trong trường hợp của tôi để liệt kê tất cả các thư mục trong một thư mục và cho mỗi thư mục sử dụng tên của thư mục làm chìa khóa cho bản đồ và sau đó gọi một phương thức với tên thư mục và trả về một bộ sưu tập các mục, trông giống như:

Map<String, Collection<ItemType>> items = Arrays.stream(itemFilesDir.listFiles(File::isDirectory))
.map(File::getName)
.collect(Collectors.toMap(s->s, this::retrieveBrandItems));

10

Bạn có thể tạo Luồng chỉ mục bằng IntStream và sau đó chuyển đổi chúng thành Bản đồ:

Map<Integer,Item> map = 
IntStream.range(0,items.size())
         .boxed()
         .collect(Collectors.toMap (i -> i, i -> items.get(i)));

1
Đây không phải là một lựa chọn tốt vì bạn thực hiện lệnh gọi get () cho mọi phần tử, do đó làm tăng độ phức tạp của thao tác (o (n * k) nếu các mục là hàm băm).
Nicolas Nobelis

Không có (i) trên hashmap O (1)?
Ivo van der Veeken

@IvovanderVeeken get (i) trong đoạn mã có trong danh sách, không phải bản đồ.
Zaki

@Zaki Tôi đã nói về nhận xét của Nicolas. Tôi không thấy độ phức tạp n * k nếu các mục là hàm băm thay vì danh sách.
Ivo van der Veeken

8

Tôi sẽ viết cách chuyển đổi danh sách thành bản đồ bằng cách sử dụng tổng quátđảo ngược điều khiển . Chỉ là phương pháp phổ quát!

Có lẽ chúng ta có danh sách các số nguyên hoặc danh sách các đối tượng. Vì vậy, câu hỏi là như sau: những gì nên là chìa khóa của bản đồ?

tạo giao diện

public interface KeyFinder<K, E> {
    K getKey(E e);
}

hiện đang sử dụng đảo ngược điều khiển:

  static <K, E> Map<K, E> listToMap(List<E> list, KeyFinder<K, E> finder) {
        return  list.stream().collect(Collectors.toMap(e -> finder.getKey(e) , e -> e));
    }

Ví dụ: nếu chúng ta có các đối tượng của cuốn sách, lớp này là chọn khóa cho bản đồ

public class BookKeyFinder implements KeyFinder<Long, Book> {
    @Override
    public Long getKey(Book e) {
        return e.getPrice()
    }
}

6

Tôi sử dụng cú pháp này

Map<Integer, List<Choice>> choiceMap = 
choices.stream().collect(Collectors.groupingBy(choice -> choice.getName()));

11
groupingBytạo ra một Map<K,List<V>>, không phải a Map<K,V>.
Christoffer Hammarström

3
Dup của vết loét trả lời. Và, String getName();(không phải số nguyên)
Barett

5
Map<String, Set<String>> collect = Arrays.asList(Locale.getAvailableLocales()).stream().collect(Collectors
                .toMap(l -> l.getDisplayCountry(), l -> Collections.singleton(l.getDisplayLanguage())));

4

Điều này có thể được thực hiện theo 2 cách. Hãy để người đó là lớp chúng ta sẽ sử dụng để chứng minh điều đó.

public class Person {

    private String name;
    private int age;

    public String getAge() {
        return age;
    }
}

Hãy để mọi người là danh sách những người được chuyển đổi thành bản đồ

1. Sử dụng foreach đơn giản và biểu thức Lambda trong danh sách

Map<Integer,List<Person>> mapPersons = new HashMap<>();
persons.forEach(p->mapPersons.put(p.getAge(),p));

2.Sử dụng Bộ sưu tập trên Luồng được xác định trong Danh sách đã cho.

 Map<Integer,List<Person>> mapPersons = 
           persons.stream().collect(Collectors.groupingBy(Person::getAge));

4

Có thể sử dụng các luồng để làm điều này. Để loại bỏ nhu cầu sử dụng rõ ràng Collectors, có thể nhập toMaptĩnh (theo khuyến nghị của Java hiệu quả, phiên bản thứ ba).

import static java.util.stream.Collectors.toMap;

private static Map<String, Choice> nameMap(List<Choice> choices) {
    return choices.stream().collect(toMap(Choice::getName, it -> it));
}


3
Map<String,Choice> map=list.stream().collect(Collectors.toMap(Choice::getName, s->s));

Thậm chí phục vụ mục đích này cho tôi,

Map<String,Choice> map=  list1.stream().collect(()-> new HashMap<String,Choice>(), 
            (r,s) -> r.put(s.getString(),s),(r,s) -> r.putAll(s));

3

Nếu mọi giá trị mới cho cùng một tên khóa phải được ghi đè:

public Map < String, Choice > convertListToMap(List < Choice > choices) {
    return choices.stream()
        .collect(Collectors.toMap(Choice::getName,
            Function.identity(),
            (oldValue, newValue) - > newValue));
}

Nếu tất cả các lựa chọn phải được nhóm trong một danh sách cho một tên:

public Map < String, Choice > convertListToMap(List < Choice > choices) {
    return choices.stream().collect(Collectors.groupingBy(Choice::getName));
}

1
List<V> choices; // your list
Map<K,V> result = choices.stream().collect(Collectors.toMap(choice::getKey(),choice));
//assuming class "V" has a method to get the key, this method must handle case of duplicates too and provide a unique key.

0

Thay thế cho guavamột người có thể sử dụng kotlin-stdlib

private Map<String, Choice> nameMap(List<Choice> choices) {
    return CollectionsKt.associateBy(choices, Choice::getName);
}

-1
String array[] = {"ASDFASDFASDF","AA", "BBB", "CCCC", "DD", "EEDDDAD"};
    List<String> list = Arrays.asList(array);
    Map<Integer, String> map = list.stream()
            .collect(Collectors.toMap(s -> s.length(), s -> s, (x, y) -> {
                System.out.println("Dublicate key" + x);
                return x;
            },()-> new TreeMap<>((s1,s2)->s2.compareTo(s1))));
    System.out.println(map);

Khóa công khai AA {12 = ASDFASDFASDF, 7 = EEDDDAD, 4 = CCCC, 3 = BBB, 2 = AA}


2
Bạn đang cố gắng làm gì ở đây ?? Bạn đã đọc câu hỏi?
navderm
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.