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.