Java: Cómo convertir Lista de Mapa
-
30-09-2019 - |
Pregunta
Recientemente tengo conversación con un colega sobre cuál sería la mejor manera de convertir a List
Map
en Java y si hay ningún beneficio específico de hacerlo.
Quiero saber enfoque conversión óptima y apreciaría realmente si alguien me puede orientar.
¿Es bueno este enfoque:
List<Object[]> results;
Map<Integer, String> resultsMap = new HashMap<Integer, String>();
for (Object[] o : results) {
resultsMap.put((Integer) o[0], (String) o[1]);
}
Solución
List<Item> list;
Map<Key,Item> map = new HashMap<Key,Item>();
for (Item i : list) map.put(i.getKey(),i);
asumiendo por supuesto que cada artículo tiene un método getKey()
que devuelve una clave del tipo adecuado.
Otros consejos
java-8 , usted será capaz de hacer esto en una línea usando arroyos , y la href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html"> breve demostración: Salida: Como se señaló en los comentarios, se puede utilizar en lugar de Y para ser nota completa que se puede utilizar un operador binario si su función no es biyectiva. Por ejemplo vamos a considerar este Cuando se ejecuta este código, se obtendrá un error que dice Aquí hay uno que suma los valores; que ahora da salida: Hope que ayuda! :) Collectors
clase Map<String, Item> map =
list.stream().collect(Collectors.toMap(Item::getKey, item -> item));
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class Test{
public static void main (String [] args){
List<Item> list = IntStream.rangeClosed(1, 4)
.mapToObj(Item::new)
.collect(Collectors.toList()); //[Item [i=1], Item [i=2], Item [i=3], Item [i=4]]
Map<String, Item> map =
list.stream().collect(Collectors.toMap(Item::getKey, item -> item));
map.forEach((k, v) -> System.out.println(k + " => " + v));
}
}
class Item {
private final int i;
public Item(int i){
this.i = i;
}
public String getKey(){
return "Key-"+i;
}
@Override
public String toString() {
return "Item [i=" + i + "]";
}
}
Key-1 => Item [i=1]
Key-2 => Item [i=2]
Key-3 => Item [i=3]
Key-4 => Item [i=4]
Function.identity()
item -> item
, aunque lo encuentro i -> i
en lugar explícita. List
y la función de mapeo que para un valor int, calcular el resultado del mismo modulo 3: List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5, 6);
Map<String, Integer> map =
intList.stream().collect(toMap(i -> String.valueOf(i % 3), i -> i));
java.lang.IllegalStateException: Duplicate key 1
. Esto se debe a 1% 3 es el mismo que 4% 3 y por lo tanto tiene el mismo valor de clave dada la función de asignación de teclas. En este caso, se puede proporcionar un operador de combinación. (i1, i2) -> i1 + i2;
que puede ser reemplazado con el Integer::sum
referencia método. Map<String, Integer> map =
intList.stream().collect(toMap(i -> String.valueOf(i % 3),
i -> i,
Integer::sum));
0 => 9 (i.e 3 + 6)
1 => 5 (i.e 1 + 4)
2 => 7 (i.e 2 + 5)
Sólo en caso de esta pregunta no es cerrado como un duplicado, la respuesta correcta es utilizar Google Colecciones :
Map<String,Role> mappedRoles = Maps.uniqueIndex(yourList, new Function<Role,String>() {
public String apply(Role from) {
return from.getName(); // or something else
}});
Desde Java 8, la respuesta por @ZouZou utilizando el colector Collectors.toMap
es sin duda la forma idiomática para resolver este problema.
Y ya que esta es una tarea tan común, podemos convertirlo en una herramienta estática.
De esta forma la solución se convierte realmente en una sola línea.
/**
* Returns a map where each entry is an item of {@code list} mapped by the
* key produced by applying {@code mapper} to the item.
*
* @param list the list to map
* @param mapper the function to produce the key from a list item
* @return the resulting map
* @throws IllegalStateException on duplicate key
*/
public static <K, T> Map<K, T> toMapBy(List<T> list,
Function<? super T, ? extends K> mapper) {
return list.stream().collect(Collectors.toMap(mapper, Function.identity()));
}
Y así es como se usaría en una List<Student>
:
Map<Long, Student> studentsById = toMapBy(students, Student::getId);
Uso de Java 8 se puede hacer lo siguiente:
Map<Key, Value> result= results
.stream()
.collect(Collectors.toMap(Value::getName,Function.identity()));
Value
puede ser cualquier objeto que utilice.
A List
y Map
son conceptualmente diferente. Un List
es una colección ordenada de elementos. Los artículos pueden contener duplicados, y un elemento que no tenga ningún concepto de un identificador único (clave). Un Map
tiene valores asignados a las teclas. Cada tecla sólo puede apuntar a un valor.
Por lo tanto, en función de los elementos de su List
, que puede o no puede ser posible para convertirlo en un Map
. ¿Tiene elementos de su List
no tienen duplicados? ¿Cada artículo tiene una clave única? Si es así, entonces es posible ponerlos en una Map
.
También hay una forma sencilla de hacer esto usando Maps.uniqueIndex (...) de Google guayaba bibliotecas
Alexis ya ha publicado una respuesta en Java 8 utilizando toMap(keyMapper, valueMapper)
método. Como por doc para este método de aplicación:
No hay garantías en cuanto al tipo, la mutabilidad, serializabilidad, o hilo de seguridad del mapa devuelto.
Así que en caso de que estén interesados ??en una implementación específica de la interfaz, por ejemplo Map
HashMap
entonces podemos utilizar la versión sobrecargada como:
Map<String, Item> map2 =
itemList.stream().collect(Collectors.toMap(Item::getKey, //key for map
Function.identity(), // value for map
(o,n) -> o, // merge function in case of conflict with keys
HashMap::new)); // map factory - we want HashMap and not any Map implementation
Aunque el uso de cualquiera Function.identity()
o i->i
está bien, pero parece Function.identity()
en lugar de i -> i
podría ahorrar algo de memoria de acuerdo con esta respuesta relacionada .
método universal
public static <K, V> Map<K, V> listAsMap(Collection<V> sourceList, ListToMapConverter<K, V> converter) {
Map<K, V> newMap = new HashMap<K, V>();
for (V item : sourceList) {
newMap.put( converter.getKey(item), item );
}
return newMap;
}
public static interface ListToMapConverter<K, V> {
public K getKey(V item);
}
Sin java-8, usted será capaz de hacer esto en las colecciones de una línea Commons, y la clase de cierre
List<Item> list;
@SuppressWarnings("unchecked")
Map<Key, Item> map = new HashMap<Key, Item>>(){{
CollectionUtils.forAllDo(list, new Closure() {
@Override
public void execute(Object input) {
Item item = (Item) input;
put(i.getKey(), item);
}
});
}};
Muchas de las soluciones vienen a la mente, dependiendo de lo que desea achive:
Cada elemento de la lista es clave y el valor
for( Object o : list ) {
map.put(o,o);
}
Los elementos de la lista tienen algo para mirar hacia arriba, tal vez un nombre:
for( MyObject o : list ) {
map.put(o.name,o);
}
Los elementos de la lista tienen algo para mirar hacia arriba, y no hay garantía de que son únicos: Uso de Google Multimapas
for( MyObject o : list ) {
multimap.put(o.name,o);
}
Dar a todos los elementos de la posición como una clave:
for( int i=0; i<list.size; i++ ) {
map.put(i,list.get(i));
}
...
Realmente depende de lo que quiere achive.
Como se puede ver en los ejemplos, un mapa es una asignación de una clave a un valor, mientras que la lista es sólo una serie de elementos que tienen una posición de cada uno. Por lo que simplemente no son automáticamente convertible.
He aquí un método poco escribí precisamente para este propósito. Utiliza Validar de Apache Commons.
Siéntase libre de usarlo.
/**
* Converts a <code>List</code> to a map. One of the methods of the list is called to retrive
* the value of the key to be used and the object itself from the list entry is used as the
* objct. An empty <code>Map</code> is returned upon null input.
* Reflection is used to retrieve the key from the object instance and method name passed in.
*
* @param <K> The type of the key to be used in the map
* @param <V> The type of value to be used in the map and the type of the elements in the
* collection
* @param coll The collection to be converted.
* @param keyType The class of key
* @param valueType The class of the value
* @param keyMethodName The method name to call on each instance in the collection to retrieve
* the key
* @return A map of key to value instances
* @throws IllegalArgumentException if any of the other paremeters are invalid.
*/
public static <K, V> Map<K, V> asMap(final java.util.Collection<V> coll,
final Class<K> keyType,
final Class<V> valueType,
final String keyMethodName) {
final HashMap<K, V> map = new HashMap<K, V>();
Method method = null;
if (isEmpty(coll)) return map;
notNull(keyType, Messages.getString(KEY_TYPE_NOT_NULL));
notNull(valueType, Messages.getString(VALUE_TYPE_NOT_NULL));
notEmpty(keyMethodName, Messages.getString(KEY_METHOD_NAME_NOT_NULL));
try {
// return the Method to invoke to get the key for the map
method = valueType.getMethod(keyMethodName);
}
catch (final NoSuchMethodException e) {
final String message =
String.format(
Messages.getString(METHOD_NOT_FOUND),
keyMethodName,
valueType);
e.fillInStackTrace();
logger.error(message, e);
throw new IllegalArgumentException(message, e);
}
try {
for (final V value : coll) {
Object object;
object = method.invoke(value);
@SuppressWarnings("unchecked")
final K key = (K) object;
map.put(key, value);
}
}
catch (final Exception e) {
final String message =
String.format(
Messages.getString(METHOD_CALL_FAILED),
method,
valueType);
e.fillInStackTrace();
logger.error(message, e);
throw new IllegalArgumentException(message, e);
}
return map;
}
Se puede aprovechar la API de Secuencia de Java 8.
public class ListToMap {
public static void main(String[] args) {
List<User> items = Arrays.asList(new User("One"), new User("Two"), new User("Three"));
Map<String, User> map = createHashMap(items);
for(String key : map.keySet()) {
System.out.println(key +" : "+map.get(key));
}
}
public static Map<String, User> createHashMap(List<User> items) {
Map<String, User> map = items.stream().collect(Collectors.toMap(User::getId, Function.identity()));
return map;
}
}
Para más detalles visita: http://codecramp.com / java-8-corrientes-api-convertir-list-mapa /
como ya se ha dicho, en java-8 tenemos la solución concisa por Collectors:
list.stream().collect(
groupingBy(Item::getKey)
)
y también, se pueden agrupar múltiples nido pasar otro método groupingBy como segundo parámetro:
list.stream().collect(
groupingBy(Item::getKey, groupingBy(Item::getOtherKey))
)
De este modo, tendremos un mapa de niveles múltiples, así: Map<key, Map<key, List<Item>>>
A Java 8 ejemplo para convertir una List<?>
de objetos en un Map<k, v>
:
List<Hosting> list = new ArrayList<>();
list.add(new Hosting(1, "liquidweb.com", new Date()));
list.add(new Hosting(2, "linode.com", new Date()));
list.add(new Hosting(3, "digitalocean.com", new Date()));
//example 1
Map<Integer, String> result1 = list.stream().collect(
Collectors.toMap(Hosting::getId, Hosting::getName));
System.out.println("Result 1 : " + result1);
//example 2
Map<Integer, String> result2 = list.stream().collect(
Collectors.toMap(x -> x.getId(), x -> x.getName()));
Código copiada de:
https://www.mkyong.com/java8/java -8-convertir-lista-para-mapa /
Me gusta la respuesta de Kango_V, pero creo que es demasiado complejo. Creo que esto es más simple - tal vez demasiado simple. Si inclinado, puede reemplazar la secuencia con un marcador genérico, y hacer que funcione para cualquier tipo de claves.
public static <E> Map<String, E> convertListToMap(Collection<E> sourceList, ListToMapConverterInterface<E> converterInterface) {
Map<String, E> newMap = new HashMap<String, E>();
for( E item : sourceList ) {
newMap.put( converterInterface.getKeyForItem( item ), item );
}
return newMap;
}
public interface ListToMapConverterInterface<E> {
public String getKeyForItem(E item);
}
Se utiliza como esto:
Map<String, PricingPlanAttribute> pricingPlanAttributeMap = convertListToMap( pricingPlanAttributeList,
new ListToMapConverterInterface<PricingPlanAttribute>() {
@Override
public String getKeyForItem(PricingPlanAttribute item) {
return item.getFullName();
}
} );
Apache Commons MapUtils.populateMap
Si usted no utiliza Java 8 y usted no desea utilizar un bucle explícito por alguna razón, trate MapUtils.populateMap
de Apache Commons.
Supongamos que tiene una lista de Pair
s.
List<ImmutablePair<String, String>> pairs = ImmutableList.of(
new ImmutablePair<>("A", "aaa"),
new ImmutablePair<>("B", "bbb")
);
Y ahora quiere un mapa de las llaves de su Pair
al objeto Pair
.
Map<String, Pair<String, String>> map = new HashMap<>();
MapUtils.populateMap(map, pairs, new Transformer<Pair<String, String>, String>() {
@Override
public String transform(Pair<String, String> input) {
return input.getKey();
}
});
System.out.println(map);
da salida:
{A=(A,aaa), B=(B,bbb)}
Una vez dicho esto, un bucle for
es quizá más fácil de entender. (Esto siguiente da la misma salida):
Map<String, Pair<String, String>> map = new HashMap<>();
for (Pair<String, String> pair : pairs) {
map.put(pair.getKey(), pair);
}
System.out.println(map);