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

820

Я хочу перевести Список объектов в Карту, используя потоки 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);
}
  • 0
    Вы можете увидеть мой ответ, как использовать дженерики и инверсию управления: stackoverflow.com/a/51334242/2590960
Теги:
java-8
lambda
java-stream

20 ответов

1231
Лучший ответ

Основываясь на Collectors documentation, он прост как:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName,
                                              Function.identity()));
  • 147
    Как примечание, даже после Java 8, JDK все еще не может участвовать в конкурсе краткости. Альтернатива Guava выглядит настолько читабельной: Maps.uniqueIndex(choices, Choice::getName) .
  • 20
    @BogdanCalmac Довольно просто написать такой uniqueIndex() в Java 8.
Показать ещё 7 комментариев
248

Если ваш ключ НЕ гарантированно уникален для всех элементов в списке, вы должны преобразовать его в Map<String, List<Choice>> вместо Map<String, Choice>

Map<String, List<Choice>> result =
 choices.stream().collect(Collectors.groupingBy(Choice::getName));
  • 74
    Это фактически дает вам Map <String, List <Choice >>, которая имеет дело с возможностью неуникальных ключей, но не то, что запрашивал OP. В Guava Multimaps.index (choices, Choice :: getName), вероятно, является лучшим вариантом, если это то, что вы хотите в любом случае.
  • 0
    или, скорее, используйте Multimap <String, Choice> в Guava, что очень удобно в сценариях, где один и тот же ключ сопоставляется с несколькими значениями. В Гуаве легко доступны различные служебные методы для использования таких структур данных вместо создания карты <String, List <Choice >>
Показать ещё 1 комментарий
127

Используйте getName() как ключ и сам выбор как значение карты:

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName, c -> c));
  • 17
    Пожалуйста, напишите некоторое описание, чтобы пользователь мог понять.
  • 4
    Плохо, что здесь нет подробностей, потому что мне больше нравится этот ответ.
Показать ещё 4 комментария
18

Здесь еще один, если вы не хотите использовать 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
    Этот пример предоставил пример того, как разместить что-то еще на карте. Я хотел значение, не предоставленное вызовом метода. Спасибо!
Показать ещё 1 комментарий
13

Большинство ответов в списке пропущено, если в списке есть повторяющиеся элементы. В этом случае ответ будет выбрасывать IllegalStateException. Обратитесь к приведенному ниже коду для обработки списка дубликатов:

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

Еще один вариант простым способом

Map<String,Choice> map = new HashMap<>();
choices.forEach(e->map.put(e.getName(),e));
  • 3
    Там нет реальной разницы в использовании этого или Java 7 типа.
  • 3
    Мне было легче читать и понимать, что происходит, чем другие механизмы.
Показать ещё 1 комментарий
12

Например, если вы хотите преобразовать поля объекта в карту:

Пример объекта:

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

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

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

Если вы не против использовать стороннюю lib (Vavr (ранее известный как Javaslang)), вы могли бы использовать мощный новый неизменный коллекции:

// import javaslang.collection.*;
Map<String, Choice> map = list.toMap(choice -> Tuple.of(choice.getName(), choice));

Существует также множество способов конвертировать Java-коллекции вперед и назад.

Подробнее о новых коллекциях здесь.

Отказ от ответственности: я создатель Вавра.

8

Я пытался это сделать и обнаружил, что, используя ответы выше, при использовании 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));
6

Вы можете создать поток индексов с помощью 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 является хеш-картой).
  • 0
    Разве get (i) не содержится в хэш-карте O (1)?
Показать ещё 2 комментария
4

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

Может быть, у нас есть список целых чисел или список объектов. Таким образом, вопрос заключается в следующем: какой должен быть ключ карты?

создать интерфейс

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

Я использую этот синтаксис

Map<Integer, List<Choice>> choiceMap = 
choices.stream().collect(Collectors.groupingBy(choice -> choice.getName()));
  • 11
    groupingBy создает Map<K,List<V>> , а не Map<K,V> .
  • 3
    Дуп ульс отвечу. И, String getName(); (не целое число)
3
Map<String, Set<String>> collect = Arrays.asList(Locale.getAvailableLocales()).stream().collect(Collectors
                .toMap(l -> l.getDisplayCountry(), l -> Collections.singleton(l.getDisplayLanguage())));
2

Для этого можно использовать потоки. Чтобы устранить необходимость явного использования 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));
}
1

Вот решение от StreamEx

StreamEx.of(choices).toMap(Choice::getName, c -> c);
0
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}

  • 0
    Что ты здесь пытаешься сделать ?? Вы читали вопрос?
0
If every new value for the same key name has to be overidden
then below will be the code :

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

If all choices have to be grouped in a list for a name then 
below will be the code:

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

Это можно сделать двумя способами. Пусть человек будет тем классом, который мы будем использовать, чтобы продемонстрировать это.

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню