representa qué ordenado metodos example entre diferencia colecciones clase java java-8 java-stream

qué - metodos de hashmap java



¿Existe una forma limpia(y nula segura) de multiplicar los valores de un mapa en Java? (12)

¿Qué tal esto?

Map<String, Double> adjustedMap = new HashMap<>(someMap); adjustedMap.entrySet().forEach(x -> { if (x.getValue() != null) { x.setValue(x.getValue() * 2); } });

Tengo un Map<String, Double> , y quiero multiplicar todos los valores en el mapa por 2, digamos, pero mantengo los nulos como nulos.

Obviamente puedo usar un bucle for para hacer esto, pero me preguntaba si había una forma más limpia de hacerlo.

Map<String, Double> someMap = someMapFunction(); Map<String, Double> adjustedMap = new Hashmap<>(); if (someMap != null) { for (Map.Entry<String,Double> pair : someMap.entryset()) { if (pair.getValue() == null) { adjustedMap.put(pair.getKey(), pair.getValue()); } else { adjustedMap.put(pair.getKey(), pair.getValue()*2) } } }

Algunas veces, el mapa devuelto por someMapFunction es un mapa inmutable, por lo que no se puede hacer en su lugar utilizando Map.replaceAll . No pude encontrar una solución de flujo que fuera más limpia.


Como alternativa a las soluciones de transmisión y / o copia, el método de utilidad Maps.transformValues() existe en Google Guava :

Map<String, Double> adjustedMap = Maps.transformValues(someMap, value -> (value != null) ? (2 * value) : null);

Esto devuelve una vista perezosa del mapa original que no realiza ningún trabajo por sí solo, pero aplica la función dada cuando es necesario. Esto puede ser tanto un profesional (si es poco probable que necesites todos los valores, esto te ahorrará tiempo de computación) como una estafa (si necesitarás el mismo valor muchas veces, o si necesitas cambiar un someMap sin adjustedMap viendo los cambios) dependiendo de su uso.


Mi primer instinto fue sugerir un Stream del entrySet Map entrada que mapea los valores a nuevos valores y termina con collectors.toMap() .

Desafortunadamente, Collectors.toMap lanza la NullPointerException cuando la función del asignador de valores devuelve null . Por lo tanto, no funciona con los valores null de su Map entrada.

Como alternativa, ya que no puede mutar su Map entrada, le sugiero que cree una copia de él y luego llame a replaceAll :

Map<String, Double> adjustedMap = new HashMap<>(someMap); adjustedMap.replaceAll ((k,v) -> v != null ? 2*v : null);


Otra forma más:

Map<String, Double> someMap = someMapFunction(); int capacity = (int) (someMap.size() * 4.0 / 3.0 + 1); Map<String, Double> adjustedMap = new HashMap<>(capacity); if (someMap != null) someMap.forEach((k, v) -> adjustedMap.put(k, v == null ? v : v * 2));

Tenga en cuenta que estoy creando el nuevo mapa con el factor de carga predeterminado ( 0.75 = 3.0 / 4.0 ) y una capacidad inicial que siempre es mayor que el size * load_factor . Esto asegura que el mapa adjustedMap nunca se redimensione / vuelva a cambiar.


Para mantener los valores nulos puede usar algo tan simple como:

someMap.keySet() .stream() .forEach(key -> adjustedMap.put(key, (someMap.get(key)) == null ? null : someMap.get(key) * 2));

Edite en respuesta al comentario de Petr Janeček : podría aplicar la propuesta en una copia de someMap :

adjustedMap.putAll(someMap); adjustedMap.keySet() .stream() .forEach(key -> adjustedMap.put(key, (adjustedMap.get(key)) == null ? null : adjustedMap.get(key) * 2));


Prueba algo como esto con java 8 steam api

Map<String, Double> newMap = oldMap.entrySet().stream() .collect(Collectors.toMap(x -> x.getKey(), x -> x.getValue() == null ? null: x.getValue()*2));


Puedes hacer esto con este código:

Map<String, Double> map = new HashMap<>(); map.put("1", 3.0); map.put("3", null); map.put("2", 5.0); Map<String, Double> res = map.entrySet() .stream() .collect( HashMap::new, (m,v)->m.put(v.getKey(), v.getValue() != null ? v.getValue() * 2 : null), HashMap::putAll ); System.out.println(res);

y la salida será:

{1 = 6.0, 2 = 10.0, 3 = nulo}

Te permitirá mantener valores null en el mapa.


Puedes lograr eso convirtiéndolo en una secuencia, con algo como:

someMap.entrySet() .forEach(entry -> { if (entry.getValue() != null) { adjustedMap.put(entry.getKey(), someMap.get(entry.getKey()) * 2); } else { adjustedMap.put(entry.getKey(), null); } });

que se puede acortar a:

someMap.forEach((key, value) -> { if (value != null) { adjustedMap.put(key, value * 2); } else { adjustedMap.put(key, null); } });

Entonces, si tienes un mapa con:

Map<String, Double> someMap = new HashMap<>(); someMap.put("test1", 1d); someMap.put("test2", 2d); someMap.put("test3", 3d); someMap.put("testNull", null); someMap.put("test4", 4d);

Obtendrá esta salida:

{test4=8.0, test2=4.0, test3=6.0, testNull=null, test1=2.0}


Se puede hacer así.

someMap.entrySet().stream() .filter(stringDoubleEntry -> stringDoubleEntry.getValue() != null) //filter null values out .forEach(stringDoubleEntry -> stringDoubleEntry.setValue(stringDoubleEntry.getValue() * 2)); //multiply values which are not null

En caso de que necesite un segundo mapa en el que solo los valores en los que no son nulos, utilice forEach para colocarlos en su nuevo mapa.


Si está de acuerdo con los valores Optional , lo siguiente puede funcionar para usted:

import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.function.Function; import static java.util.stream.Collectors.toMap; public static Map<String, Optional<Double>> g(Map<String, Double> map, Function<Double, Double> f) { return map.entrySet().stream().collect( toMap( e -> e.getKey(), e -> e.getValue() == null ? Optional.empty() : Optional.of(f.apply(e.getValue())) )); }

y entonces:

public static void main(String[] args) throws Exception { Map<String, Double> map = new HashMap<>(); map.put("a", 2.0); map.put("b", null); map.put("c", 3.0); System.out.println(g(map, x -> x * 2)); System.out.println(g(map, x -> Math.sin(x))); }

huellas dactilares:

{a=Optional[4.0], b=Optional.empty, c=Optional[6.0]} {a=Optional[0.9092974268256817], b=Optional.empty, c=Optional[0.1411200080598672]}

Esto está bastante limpio con la creación del nuevo mapa delegado a los Collectors y el beneficio adicional del tipo de devolución Map<String, Optional<Double>> que indica claramente la posibilidad de null y alienta a los usuarios a manejarlos.


Utilice de esta manera.

Map<String, Double> adjustedMap = map.entrySet().stream().filter(x -> x.getValue() != null) .collect(Collectors.toMap(x -> x.getKey(), x -> 2*x.getValue())); //printing adjustedMap.entrySet().stream().forEach(System.out::println);


Ya hay muchas respuestas. Algunos de ellos me parecen un poco dudosos. En cualquier caso, la mayoría de ellos incluyen el cheque null de una forma u otra.

Un enfoque que da un paso en la escalera de abstracción es el siguiente:

Desea aplicar un operador unario a los valores del mapa. Así que puedes implementar un método que aplique un operador unario a los valores del mapa. (Hasta ahora tan bueno). Ahora, desea un operador unario "especial" que sea null -seguro. Luego, puede envolver un operador unario seguro y null alrededor del original.

Esto se muestra aquí, con tres operadores diferentes (uno de ellos es Math::sin , en realidad):

import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.function.UnaryOperator; public class MapValueOps { public static void main(String[] args) { Map<String, Double> map = new LinkedHashMap<String, Double>(); map.put("A", 1.2); map.put("B", 2.3); map.put("C", null); map.put("D", 4.5); Map<String, Double> resultA = apply(map, nullSafe(d -> d * 2)); System.out.println(resultA); Map<String, Double> resultB = apply(map, nullSafe(d -> d + 2)); System.out.println(resultB); Map<String, Double> resultC = apply(map, nullSafe(Math::sin)); System.out.println(resultC); } private static <T> UnaryOperator<T> nullSafe(UnaryOperator<T> op) { return t -> (t == null ? t : op.apply(t)); } private static <K> Map<K, Double> apply( Map<K, Double> map, UnaryOperator<Double> op) { Map<K, Double> result = new LinkedHashMap<K, Double>(); for (Entry<K, Double> entry : map.entrySet()) { result.put(entry.getKey(), op.apply(entry.getValue())); } return result; } }

Creo que esto está limpio, porque separa bien las preocupaciones de aplicar el operador y realizar la comprobación null . Y es null seguro, porque ... el nombre del método lo dice.

(Se podría argumentar que la llamada para nullSafe al operador en un nullSafe en el método de apply , pero ese no es el punto aquí)

Editar:

Dependiendo del patrón de aplicación deseado, uno podría hacer algo similar y aplicar la transformación en su lugar , sin crear un nuevo mapa, llamando a Map#replaceAll