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


934

Я хочу перевести Список объектов в Карту, используя потоки и лямбды 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();
        }
    });
}

И гуава с ява 8 лямбд.

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

Ответы:


1396

На основании 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 >>, которая имеет дело с возможностью неуникальных ключей, но не то, что запрашивал OP. В Guava Multimaps.index (choices, Choice :: getName), вероятно, является лучшим вариантом, если это то, что вам нужно в любом случае.
Ричард Николс

или, скорее, используйте Multimap <String, Choice> в Guava, что очень удобно в сценариях, где один и тот же ключ сопоставляется с несколькими значениями. В Гуаве легко доступны различные служебные методы для использования таких структур данных вместо создания карты <String, List <Choice >>
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 символа короче)
Ferrybig

7
Это равно choices.stream().collect(Collectors.toMap(choice -> choice.getName(),choice -> choice)); первой функции для ключа, второй функции для значения
Waterscar

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, как вы показали в примере выше?
Свапнил Ганград

1
Этот пример предоставил пример того, как разместить что-то еще на карте. Я хотел значение, не предоставленное вызовом метода. Спасибо!
th3morg

1
Функция третьего аргумента неверна. Там вы должны предоставить некоторую функцию для слияния двух Hashmaps, что-то вроде 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 типа.
Ашиш Лохия

3
Мне было легче читать и понимать, что происходит, чем другие механизмы
Freiheit

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

12

Если вы не возражаете против использования сторонних библиотек, библиотека AOL cyclops-реагировать (раскрытие которой я являюсь автором) имеет расширения для всех типов JDK Collection , включая List и Map .

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

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), если items является хеш-картой).
Николас Нобелис

Разве get (i) не содержится в хэш-карте O (1)?
Иво ван дер Викен

@IvovanderVeeken get (i) во фрагменте кода находится в списке, а не на карте.
Заки

@ Заки, я говорил о замечании Николаса. Я не вижу сложности 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. Использование простого foreach и лямбда-выражения в списке

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статически (в соответствии с рекомендациями Effective 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.


-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
Что ты здесь пытаешься сделать ?? Вы читали вопрос?
Навдерм
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.