Java 8 Список <V> на карту <K, V>


932

Я хочу перевести Список об’єктів на карту за допомогою потоків і лямбдав Java 8.

Ось як я написав би це на Java 7 і нижче.

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

Я можу це легко досягти, використовуючи Java 8 та Guava, але я хотів би знати, як це зробити без Guava.

У Гуаві:

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

І Гуава з лямбдами Java 8.

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

Відповіді:


1394

На основі Collectorsдокументації це так просто, як:

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

167
Як зауваження, навіть після Java 8, JDK все ще не може конкурувати в короткості. Гуави альтернативних поглядів так багато читають: Maps.uniqueIndex(choices, Choice::getName).
Богдан Кальмак

3
Використовуючи (статично імпортований) Seqз бібліотеки JOOL (який я рекомендую всім, хто використовує Java 8), ви також можете покращити стислість за допомогою: seq(choices).toMap(Choice::getName)
lukens

5
Чи є якісь переваги від використання Function.identity? Я маю на увазі, це -> це коротше
shabunc

9
@shabunc Я не знаю жодної користі і фактично використовую it -> itсебе. Function.identity()тут використовується здебільшого тому, що він використовується у згаданій документації, і це було все, що я знав про лямбда на момент написання
zapl

12
@zapl, ну, на самому ділі виявляється, є причини цього - stackoverflow.com/questions/28032827 / ...
shabunc

307

Якщо ваш ключ НЕ гарантовано є унікальним для всіх елементів у списку, слід перетворити його на Map<String, List<Choice>>замість аMap<String, Choice>

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

76
Це фактично дає вам Map <String, List <Choice>>, який стосується можливості унікальних ключів, але це не те, що вимагала ОП. У Гуаві Multimaps.index (вибір, вибір: getName), мабуть, є кращим варіантом, якщо це те, що ви хочете так чи інакше.
Річард Ніколс

або скоріше скористайтеся мультимапами Guava <String, Choice>, що дуже зручно в сценаріях, де однаковий ключ відображає кілька значень. У Guava легко доступні різні корисні методи для використання таких структур даних, а не для створення карти <Строка, Список <Вибір>>
Neeraj B.

1
@RichardNichols Чому Multimapsметод гуави є кращим варіантом? Це може бути незручністю, оскільки він не повертає MapОб'єкт.
вони

156

Використовувати getName()як ключ і Choiceсебе як значення карти:

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

19
Будь ласка, напишіть якийсь опис, щоб користувач міг зрозуміти.
Mayank Jain

4
Це дуже погано, що деталей тут більше немає, тому що мені найкраще подобається ця відповідь.
MadConan

Collectors.toMap(Choice::getName,c->c)(На 2
рази

7
Це дорівнює choices.stream().collect(Collectors.toMap(choice -> choice.getName(),choice -> choice)); першій функції для ключа, другій функції за значенням
водний автомобіль

16
Я знаю, як легко бачити і розуміти, c -> cале Function.identity()несе більш семантичну інформацію. Я зазвичай використовую статичний імпорт, щоб я міг просто використовуватиidentity()
Хенк Д

28

Ось ще один випадок, якщо ви не хочете використовувати Collectors.toMap ()

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

1
Що краще використовувати тоді Collectors.toMap()чи наш власний HashMap, як ви показали у наведеному вище прикладі?
Swapnil Gangrade

1
Цей приклад подав приклад того, як розмістити щось інше на карті. Я хотів значення, яке не надається викликом методу. Дякую!
th3morg

1
Функція третього аргументу неправильна. Там ви повинні надати певну функцію для злиття двох Hashmap, щось на зразок Hashmap :: putAll
jesantana

25

Більшість перерахованих відповідей пропускають випадок, коли у списку є дублікати елементів . У такому випадку відповідь буде кинутий IllegalStateException. Нижче наведено код для обробки дублікатів списку :

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

19

Ще один варіант простим способом

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

3
Немає різної життєздатності у використанні цього типу або типу java 7.
Ashish Lohia

3
Мені це було легше читати і розуміти, що відбувається, ніж інші механізми
Фрейхейт

2
SO запитав у Java 8 Streams.
Мехрай Малик

18

Наприклад, якщо ви хочете перетворити об’єктні поля на карту:

Приклад об'єкта:

class Item{
        private String code;
        private String name;

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

        //getters and setters
    }

І операція конвертує список у карту:

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


10

Я намагався це зробити і виявив, що, використовуючи відповіді, наведені вище, під час використання Functions.identity()ключа на карті, тоді у мене виникли проблеми з використанням локального методу, який би this::localMethodNameнасправді працював через введення проблем.

Functions.identity()насправді щось робить для набору тексту в цьому випадку, щоб метод працював лише шляхом повернення Objectта прийняття парамObject

Щоб вирішити це, я закінчився ровом Functions.identity()і s->sзамість цього.

Тож мій код, у моєму випадку, щоб перелічити всі каталоги всередині каталогу, і для кожного з них використовувати ім'я каталогу як ключ до карти, а потім викликати метод із назвою каталогу та повернути колекцію елементів, виглядає так:

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

10

Ви можете створити потік індексів за допомогою IntStream і потім перетворити їх на карту:

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

1
Це не гарний варіант, оскільки ви здійснюєте виклик get () для кожного елемента, отже збільшуєте складність операції (o (n * k), якщо елементи - хешмап).
Ніколя Нобеліс

Не потрапляє (i) на хешмап O (1)?
Іво ван дер Векен

@IvovanderVeeken get (i) у фрагменті коду є у списку, а не на карті.
Закі

@Zaki Я говорив про зауваження Ніколя. Я не бачу складності n * k, якщо елементи є хешмапом замість списку.
Іво ван дер Векен

8

Я напишу, як перетворити список на карту за допомогою дженерики та інверсії управління . Просто універсальний метод!

Можливо, у нас є список Цілих чилів або список об'єктів. Тож питання таке: що має бути ключовим у карті?

створити інтерфейс

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

Тепер використовується інверсія управління:

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

Наприклад, якщо у нас є об'єкти книги, для цього класу слід вибрати ключ для карти

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


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

4

Це можна зробити двома способами. Нехай людина буде класом, який ми будемо використовувати для демонстрації.

public class Person {

    private String name;
    private int age;

    public String getAge() {
        return age;
    }
}

Нехай люди є списком осіб, які потрібно перетворити на карту

1. Використання простого передбачення та висловлювання лямбда у списку

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

2. Використання колекторів у потоці, визначених у даному списку.

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

4

Для цього можна використовувати потоки. Щоб усунути необхідність явного використання Collectors, можна імпортувати toMapстатично (як рекомендує Ефективна Java, третє видання).

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

Навіть служить цій цілі для мене,

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

3

Якщо кожне нове значення для одного і того ж імені ключа має бути замінено:

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

Якщо всі варіанти мають бути згруповані у списку для імені:

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

В якості альтернативи guavaможна використовувати 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);

Дублікат ключа AA {12 = ASDFASDFASDF, 7 = EEDDDAD, 4 = CCCC, 3 = BBB, 2 = AA}


2
Що ти тут намагаєшся зробити ?? Ви читали питання?
navderm
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.