ConcurrentHashMap JDK 8 cuándo usar ComputeIfPresent
La nueva versión de Concurrent Hash Map de jdk 8 tiene dos nuevos métodos.
calcular si está ausente
calcular si presente
putIfAbsent - Método antiguo
Entiendo los casos de uso de putIfAbsent y ComputeIfAbsent . Pero no estoy seguro de los escenarios en los que usaré ComputeIfPresent . Además, ¿por qué necesito putIfAbsent si tengo ComputeIfPresent ahora? putIfAbsent crea al menos una instancia adicional del valor.
¿La razón es solo para tener compatibilidad con versiones anteriores?
Como se menciona en la otra respuesta: los métodos siempre se conservarán para garantizar la compatibilidad con versiones anteriores, incluso si se introducen métodos nuevos y más "poderosos".
Con respecto al caso de uso de computeIfPresent
: Puede resultar difícil encontrar un ejemplo que sea lo suficientemente pequeño como para no parecer artificial y aun así ser convincente. En general, la intención de este método es actualizar un valor existente en cualquier forma.
Un ejemplo podría ser un recuento de palabras (restringido) : para un conjunto determinado de palabras, se almacena un recuento inicial 0
en el mapa. Luego, se procesa una secuencia de palabras: cada vez que se encuentra una palabra del conjunto inicial, su recuento aumenta en 1:
import java.util.LinkedHashMap;
import java.util.Map;
public class ComputeIfPresentExample
{
public static void main(String[] args)
{
Map<String, Integer> wordCounts = new LinkedHashMap<String, Integer>();
String s =
"Lorem ipsum dolor sit amet consetetur iam nonumy sadipscing " +
"elitr, sed diam nonumy eirmod tempor invidunt ut erat sed " +
"labore et dolore magna dolor sit amet aliquyam erat sed diam";
wordCounts.put("sed", 0);
wordCounts.put("erat", 0);
for (String t : s.split(" "))
{
wordCounts.computeIfPresent(t, (k,v) -> v+1);
}
System.out.println(wordCounts);
}
}
(Por supuesto, cosas como esta podrían resolverse de otra manera, pero esta es una tarea bastante frecuente de una forma u otra, y el nuevo método permite una solución bastante concisa y elegante)
Un caso de uso común son los mapas con colecciones , como
Map<String, Collection<String>> strings = new HashMap<>();
computeIfAbsent
y computeIfPresent
son operaciones muy útiles para agregar y eliminar elementos a/de la colección. Sobre todo porque, a diferencia de put()
, los compute*()
métodos devuelven el valor actual (ya sea que se haya creado recientemente o no). A continuación se muestra un ejemplo que agrupa cadenas por su primer carácter. Tenga en cuenta que tanto las claves como las colecciones se crean cuando es necesario y se limpian cuando la colección queda vacía:
void addString(String a) {
String index = a.substring(0, 1);
strings.computeIfAbsent(index, ign -> new HashSet<>()).add(a);
}
void removeString(String a) {
String index = a.substring(0, 1);
strings.computeIfPresent(index, (k, c) -> {
c.remove(a);
return c.isEmpty() ? null : c;
});
}
Ejemplo:
// {}
addString("a1"); // {a=[a1]} <-- collection dynamically created
addString("a2"); // {a=[a1, a2]}
removeString("a1"); // {a=[a2]}
removeString("a2"); // {} <-- both key and collection removed
Esto es extremadamente poderoso en entornos multiproceso ya que ConcurrentMaps
realiza estas operaciones de forma atómica.
La operación de eliminación puede ser de una sola línea:
void removeString(String a) {
String index = a.substring(0, 1);
strings.computeIfPresent(index, (i, c) -> c.remove(a) && c.isEmpty() ? null : c);
}
Así que una vez más en resumen:
Map<String, Set<String>> map = new ConcurrentHashMap<>();
map.computeIfAbsent(key, i -> ConcurrentHashMap.newKeySet()).add(value);
map.computeIfPresent(key, (i, s) -> s.remove(value) && s.isEmpty() ? null : s);