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);
}
Tom Cammann
fuente

Respuestas:

1395

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

Map<String, Choice> result =
    choices.stream().collect(Collectors.toMap(Choice::getName,
                                              Function.identity()));
zapl
fuente
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));
Ulises
fuente
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));
Viejo
fuente
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) -> {});
Emre Colak
fuente
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));
  }
Sahil Chhabra
fuente
19

Una opción más de manera simple

Map<String,Choice> map = new HashMap<>();
choices.forEach(e->map.put(e.getName(),e));
Renukeswar
fuente
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));
Piotr R
fuente
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);
John McClean
fuente
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));
iZian
fuente
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)));
Vikas Suryawanshi
fuente
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()
    }
}
grep
fuente
6

Yo uso esta sintaxis

Map<Integer, List<Choice>> choiceMap = 
choices.stream().collect(Collectors.groupingBy(choice -> choice.getName()));
usuario2069723
fuente
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())));
Kumar Abhishek
fuente
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));
raja emani
fuente
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));
}
Konrad Borowski
fuente
3

Aquí hay una solución de StreamEx

StreamEx.of(choices).toMap(Choice::getName, c -> c);
user_3380739
fuente
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));
Rajeev Akotkar
fuente
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));
}
Vaneet Kataria
fuente
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.
vaibhav
fuente
0

Como alternativa a guavauno, puede usar kotlin-stdlib

private Map<String, Choice> nameMap(List<Choice> choices) {
    return CollectionsKt.associateBy(choices, Choice::getName);
}
Frank Neblung
fuente
-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}

Ajay Kumar
fuente
2
Qué está tratando de hacer aquí ?? ¿Leíste la pregunta?
navderm