Lista do Java 8 <V> no mapa <K, V>


932

Eu quero traduzir uma lista de objetos em um mapa usando fluxos e lambdas do Java 8.

É assim que eu escreveria em Java 7 e abaixo.

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

Posso fazer isso facilmente usando Java 8 e Guava, mas gostaria de saber como fazer isso sem o Guava.

Na goiaba:

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

E Goiaba com Java 8 lambdas.

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

Respostas:


1394

Com base na Collectorsdocumentação , é tão simples quanto:

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

167
Como observação, mesmo após o Java 8, o JDK ainda não pode competir em um concurso de concisão. A goiaba olhares alternativos tanto legíveis: Maps.uniqueIndex(choices, Choice::getName).
Bogdan Calmac 03/03

3
Usando (importado estaticamente) Seqda biblioteca JOOL (que eu recomendo para qualquer um que use o Java 8), você também pode melhorar a brevidade com: seq(choices).toMap(Choice::getName)
lukens

5
Há algum benefício em usar o Function.identity? Quero dizer, -> é mais curto #
shabunc

9
@shabunc Eu não conheço nenhum benefício e realmente it -> itme uso . Function.identity()é usada aqui principalmente porque ele é usado na documentação referida e isso era tudo que eu sabia sobre lambdas no momento da escrita
zapl

12
@zapl, oh, na verdade, verifica-se há razões por trás disso - stackoverflow.com/questions/28032827/...
shabunc

307

Se NÃO for garantido que sua chave seja exclusiva para todos os elementos da lista, você deve convertê-la em um em Map<String, List<Choice>>vez de umMap<String, Choice>

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

76
Na verdade, isso fornece Map <String, List <Choice>> que trata da possibilidade de chaves não exclusivas, mas não é o que o OP solicitou. No Guava, o Multimaps.index (escolhas, Choice :: getName) é provavelmente uma opção melhor, se é isso que você deseja.
Richard Nichols

ou melhor, use o Multimap <String, Choice> do Guava, que é bastante útil em cenários em que a mesma chave é mapeada para vários valores. Existem vários métodos de utilidade prontamente disponíveis na goiaba para usar essas estruturas de dados em vez de criar um Map <String, List <Choice >>
Neeraj B.

1
@RichardNichols Por que o Multimapsmétodo goiaba é uma opção melhor? Pode ser um inconveniente, pois não retorna um Mapobjeto.
theyuv 01/11/19

156

Use getName()como chave e Choiceele próprio como o valor do mapa:

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

19
Por favor, escreva alguma descrição para que o usuário possa entender.
precisa

4
É realmente muito ruim que não haja mais detalhes aqui, porque eu gosto mais desta resposta.
MadConan

Collectors.toMap(Choice::getName,c->c)(2 caracteres mais curtos)
Ferrybig 26/11/2015

7
É igual a choices.stream().collect(Collectors.toMap(choice -> choice.getName(),choice -> choice)); primeira função de chave, segunda função para o valor
waterscar

16
Sei como é fácil ver e entender, c -> cmas Function.identity()carrega mais informações semânticas. Eu costumo usar uma importação estática para que eu possa usar apenasidentity()
Hank D

28

Aqui está outro caso você não queira usar Collectors.toMap ()

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

1
Qual é melhor usar então Collectors.toMap()ou nosso próprio HashMap, como você mostrou no exemplo acima?
Swapnil Gangrade

1
Este exemplo forneceu um exemplo de como colocar outra coisa no mapa. Eu queria um valor não fornecido por uma chamada de método. Obrigado!
th3morg

1
A terceira função de argumento não está correta. Lá você deve fornecer alguma função para fundir duas HashMaps, algo como HashMap :: putAll
jesantana

25

A maioria das respostas listadas perde um caso em que a lista possui itens duplicados . Nesse caso, a resposta será lançada IllegalStateException. Consulte o código abaixo para lidar com duplicatas da lista também:

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

19

Mais uma opção de maneira simples

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

3
Não há diferença viável no uso deste ou do tipo java 7.
Ashish Lohia

3
Achei isso mais fácil de ler e entender o que estava acontecendo do que os outros mecanismos
Freiheit

2
O SO perguntou com o Java 8 Streams.
Mehraj Malik 30/0318

18

Por exemplo, se você deseja converter campos de objetos em mapeamento:

Objeto de exemplo:

class Item{
        private String code;
        private String name;

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

        //getters and setters
    }

E a operação converte Lista em Mapa:

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

Se você não se importa em usar bibliotecas de terceiros, a lib cyclops-react da AOL (divulgação eu sou um colaborador) possui extensões para todos os tipos de coleções do JDK , incluindo Lista e Mapa .

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

10

Eu estava tentando fazer isso e descobri que, usando as respostas acima, ao usar Functions.identity()a chave do mapa, tive problemas ao usar um método local comothis::localMethodName de fato, funciona por causa de problemas de digitação.

Functions.identity() realmente faz alguma coisa com a digitação neste caso, então o método só funcionaria retornando Object e aceitando um parâmetro deObject

Para resolver isso, acabei abandonando Functions.identity()e usandos->s .

Portanto, meu código, no meu caso, para listar todos os diretórios dentro de um diretório, e para cada um deles usar o nome do diretório como a chave do mapa e, em seguida, chamar um método com o nome do diretório e retornar uma coleção de itens, se parece com:

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

10

Você pode criar um fluxo de índices usando um IntStream e depois convertê-los em um mapa:

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

1
Essa não é uma boa opção porque você faz uma chamada get () para cada elemento, aumentando assim a complexidade da operação (o (n * k) se itens é um mapa de hash).
Nicolas Nobelis

O get (i) não está em um hashmap O (1)?
Ivo van der Veeken 18/09/18

@IvovanderVeeken o get (i) no snippet de código está na lista, não no mapa.
Zaki

@Zaki Eu estava falando sobre o comentário de Nicolas. Não vejo a complexidade n * k se itens é um mapa de hash em vez de uma lista.
Ivo van der Veeken

8

Vou escrever como converter lista em mapa usando genéricos e inversão de controle . Apenas método universal!

Talvez tenhamos lista de Inteiros ou lista de objetos. Portanto, a pergunta é a seguinte: qual deve ser a chave do mapa?

criar interface

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

agora usando inversão de controle:

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

Por exemplo, se tivermos objetos de livro, essa classe será para escolher a chave do mapa

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

6

Eu uso essa sintaxe

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

11
groupingBycria um Map<K,List<V>>, não um Map<K,V>.
Christoffer Hammarström

3
Dup de ulises responder. E, String getName();(não inteiro)
Barett

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

4

Isso pode ser feito de duas maneiras. Seja pessoa a classe que vamos usar para demonstrá-la.

public class Person {

    private String name;
    private int age;

    public String getAge() {
        return age;
    }
}

Permita que as pessoas sejam a lista de Pessoas a serem convertidas no mapa

1.Usando o foreach simples e uma expressão lambda na lista

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

2.Usando coletores no fluxo definido na lista fornecida.

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

4

É possível usar fluxos para fazer isso. Para remover a necessidade de usar explicitamente Collectors, é possível importar toMapestaticamente (conforme recomendado por Effective Java, terceira edição).

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

Até serve esse propósito para mim,

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

3

Se todo novo valor para o mesmo nome de chave tiver que ser substituído:

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

Se todas as opções tiverem que ser agrupadas em uma lista para um nome:

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

Como alternativa, guavapode-se usar o 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);

Divulgue a chave AA {12 = ASDFASDFASDF, 7 = EEDDDAD, 4 = CC0, 3 = BBB, 2 = AA}


2
O que você está tentando fazer aqui ?? Você leu a pergunta?
Navderm 25/03/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.