Java 8 Lista <V> en el Mapa <K, V>


934

Quiero traducir una Lista de objetos en un Mapa usando los flujos y lambdas de Java 8.

Así es como lo escribiría en Java 7 y a continuación.

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

Puedo lograr esto fácilmente usando Java 8 y Guava, pero me gustaría saber cómo hacerlo sin Guava.

En guayaba:

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

Y guayaba con Java 8 lambdas.

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

Respuestas:


1395

Según la Collectorsdocumentación , es tan simple como:

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

167
Como nota al margen, incluso después de Java 8, el JDK todavía no puede competir en un concurso de brevedad. La guayaba miradas alternativas tanto legibles: Maps.uniqueIndex(choices, Choice::getName).
Bogdan Calmac

3
Usando (importado estáticamente) Seqde la biblioteca JOOL (que recomendaría a cualquiera que use Java 8), también puede mejorar la brevedad con: seq(choices).toMap(Choice::getName)
lukens

55
¿Hay algún beneficio al usar Function.identity? Quiero decir, es -> es más corto
shabunc

99
@shabunc No conozco ningún beneficio y en realidad it -> itme uso . Function.identity()se usa aquí principalmente porque se usa en la documentación referenciada y eso era todo lo que sabía sobre lambdas en el momento de escribir esto
zapl

12
@zapl, oh, en realidad resulta que hay razones detrás de esto - stackoverflow.com/questions/28032827/…
shabunc

307

Si NO se garantiza que su clave sea ​​única para todos los elementos de la lista, debe convertirla en un en Map<String, List<Choice>>lugar de unMap<String, Choice>

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

76
En realidad, esto le proporciona Map <String, List <Choice>> que trata con la posibilidad de claves no únicas, pero no es lo que solicitó el OP. En Guava, Multimaps.index (elecciones, Choice :: getName) es probablemente una mejor opción si de todos modos esto es lo que desea.
Richard Nichols

o más bien, use el Multimap <String, Choice> de Guava, que es bastante útil en escenarios donde la misma clave se asigna a múltiples valores. Hay varios métodos de utilidad fácilmente disponibles en Guava para usar tales estructuras de datos en lugar de crear un Mapa <String, List <Choice>>
Neeraj B.

1
@RichardNichols por qué es la guayaba Multimaps método de es una mejor opción? Puede ser un inconveniente ya que no devuelve un MapObjeto.
Theyuv

156

Úselo getName()como la clave y Choicecomo el valor del mapa:

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

19
Por favor escriba alguna descripción para que el usuario pueda entender.
Mayank Jain

44
Es realmente una lástima que no haya más detalles aquí, porque me gusta más esta respuesta.
MadConan

Collectors.toMap(Choice::getName,c->c)(2 caracteres más cortos)
Ferrybig

77
Es igual a la choices.stream().collect(Collectors.toMap(choice -> choice.getName(),choice -> choice)); primera función para la tecla, la segunda función para el valor
waterscar

16
Sé lo fácil que es ver y comprender, c -> cpero Function.identity()lleva más información semántica. Usualmente uso una importación estática para poder usarlaidentity()
Hank D

28

Aquí hay otro en caso de que no desee utilizar Collectors.toMap ()

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

1
¿Cuál es mejor usar entonces Collectors.toMap()o nuestro propio HashMap como lo mostró en el ejemplo anterior?
Swapnil Gangrade

1
Este ejemplo proporcionó un ejemplo de cómo colocar algo más en el mapa. Quería un valor no proporcionado por una llamada al método. ¡Gracias!
th3morg

1
La función del tercer argumento no es correcta. Allí debe proporcionar alguna función para fusionar dos Hashmaps, algo así como Hashmap :: putAll
jesantana

25

La mayoría de las respuestas enumeradas, pierden un caso cuando la lista tiene elementos duplicados . En ese caso la respuesta arrojará IllegalStateException. Consulte el siguiente código para manejar los duplicados de la lista también:

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

19

Una opción más de manera simple

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

3
No hay una diferencia viable en el uso de este tipo de Java 7.
Ashish Lohia

3
Encontré esto más fácil de leer y entender lo que estaba sucediendo que los otros mecanismos
Freiheit

2
El SO preguntó con Java 8 Streams.
Mehraj Malik

18

Por ejemplo, si desea convertir campos de objeto para asignar:

Objeto de ejemplo:

class Item{
        private String code;
        private String name;

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

        //getters and setters
    }

Y la operación convierte Lista a 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

Si no le importa usar bibliotecas de terceros, la biblioteca cyclops-react lib de AOL (revelación de que soy colaborador) tiene extensiones para todos los tipos de colección JDK , incluidos List y Map .

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

10

Estaba tratando de hacer esto y descubrí que, usando las respuestas anteriores, cuando usaba Functions.identity()la clave del Mapa, tenía problemas con el uso de un método local comothis::localMethodName que realmente funcionaba debido a problemas de tipeo.

Functions.identity() en realidad hace algo al escribir en este caso, por lo que el método solo funcionaría al devolver Object y aceptar un parámetro deObject

Para resolver esto, terminé abandonando Functions.identity()y usandos->s lugar.

Entonces, mi código, en mi caso para enumerar todos los directorios dentro de un directorio, y para cada uno usar el nombre del directorio como la clave del mapa y luego llamar a un método con el nombre del directorio y devolver una colección de elementos, se ve así:

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

10

Puede crear una secuencia de los índices utilizando un IntStream y luego convertirlos en un mapa:

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

1
Esta no es una buena opción porque realiza una llamada get () para cada elemento, por lo tanto, aumenta la complejidad de la operación (o (n * k) si los elementos son un mapa hash).
Nicolas Nobelis

¿No es get (i) en un hashmap O (1)?
Ivo van der Veeken

@IvovanderVeeken el get (i) en el fragmento de código está en la lista, no en el mapa.
Zaki

@Zaki Estaba hablando del comentario de Nicolas. No veo la complejidad n * k si los elementos son un hashmap en lugar de una lista.
Ivo van der Veeken

8

Escribiré cómo convertir la lista al mapa usando genéricos e inversión de control . ¡Solo método universal!

Tal vez tenemos una lista de enteros o una lista de objetos. Entonces la pregunta es la siguiente: ¿cuál debería ser la clave del mapa?

crear interfaz

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

ahora usando inversión de control:

  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 ejemplo, si tenemos objetos de libro, esta clase es elegir clave para el mapa

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

6

Yo uso esta sintaxis

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

11
groupingBycrea un Map<K,List<V>>, no un Map<K,V>.
Christoffer Hammarström

3
Dup de respuesta ulises. Y, String getName();(no Entero)
Barett

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

4

Esto se puede hacer de 2 maneras. Deje que la persona sea la clase que vamos a usar para demostrarlo.

public class Person {

    private String name;
    private int age;

    public String getAge() {
        return age;
    }
}

Dejar personas sean la lista de personas que se convertirán al mapa

1.Utilizando Foreach simple y una expresión Lambda en la lista

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

2.Utilizando Collectors on Stream definidos en la Lista dada.

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

4

Es posible usar transmisiones para hacer esto. Para eliminar la necesidad de usar explícitamente Collectors, es posible importar toMapestáticamente (como lo recomienda Effective Java, tercera edición).

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

Incluso sirve este propósito para mí,

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

3

Si cada nuevo valor para el mismo nombre de clave tiene que ser anulado:

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

Si todas las opciones tienen que agruparse en una lista para un nombre:

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 a guavauno, puede usar 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);

Clave duplicada AA {12 = ASDFASDFASDF, 7 = EEDDDAD, 4 = CCCC, 3 = BBB, 2 = AA}


2
Qué está tratando de hacer aquí ?? ¿Leíste la pregunta?
navderm
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.