Guía de Guayaba Multiset

1. Información general

En este tutorial, exploraremos una de las colecciones de Guava: Multiset . Al igual que un java.util.Set , permite el almacenamiento y la recuperación eficientes de elementos sin un pedido garantizado.

Sin embargo, a diferencia de un conjunto , permite múltiples apariciones del mismo elemento mediante el seguimiento del recuento de cada elemento único que contiene.

2. Dependencia de Maven

Primero, agreguemos la dependencia de guayaba :

 com.google.guava guava 29.0-jre 

3. Usando Multiset

Consideremos una librería que tiene múltiples copias de diferentes libros. Es posible que deseemos realizar operaciones como agregar una copia, obtener la cantidad de copias y eliminar una copia cuando se vende. Como un conjunto no permite múltiples apariciones del mismo elemento, no puede manejar este requisito.

Comencemos agregando copias del título de un libro. El Multiset debería devolver que el título existe y proporcionarnos el recuento correcto :

Multiset bookStore = HashMultiset.create(); bookStore.add("Potter"); bookStore.add("Potter"); bookStore.add("Potter"); assertThat(bookStore.contains("Potter")).isTrue(); assertThat(bookStore.count("Potter")).isEqualTo(3);

Ahora eliminemos una copia. Esperamos que el recuento se actualice en consecuencia:

bookStore.remove("Potter"); assertThat(bookStore.contains("Potter")).isTrue(); assertThat(bookStore.count("Potter")).isEqualTo(2);

Y, de hecho, podemos simplemente establecer el recuento en lugar de realizar varias operaciones de adición:

bookStore.setCount("Potter", 50); assertThat(bookStore.count("Potter")).isEqualTo(50);

Multiset valida el valor de recuento . Si lo establecemos en negativo, se lanza una IllegalArgumentException :

assertThatThrownBy(() -> bookStore.setCount("Potter", -1)) .isInstanceOf(IllegalArgumentException.class);

4. Comparación con Map

Sin acceso a Multiset , podríamos lograr todas las operaciones anteriores implementando nuestra propia lógica usando java.util.Map:

Map bookStore = new HashMap(); // adding 3 copies bookStore.put("Potter", 3); assertThat(bookStore.containsKey("Potter")).isTrue(); assertThat(bookStore.get("Potter")).isEqualTo(3); // removing 1 copy bookStore.put("Potter", 2); assertThat(bookStore.get("Potter")).isEqualTo(2);

Cuando queremos agregar o eliminar una copia usando un mapa , necesitamos recordar el recuento actual y ajustarlo en consecuencia. También necesitamos implementar esta lógica en nuestro código de llamada cada vez o construir nuestra propia biblioteca para este propósito. Nuestro código también necesitaría controlar el argumento de valor . Si no tenemos cuidado, podríamos establecer fácilmente el valor en nulo o negativo aunque ambos valores no sean válidos:

bookStore.put("Potter", null); assertThat(bookStore.containsKey("Potter")).isTrue(); bookStore.put("Potter", -1); assertThat(bookStore.containsKey("Potter")).isTrue(); 

Como podemos ver, es mucho más conveniente utilizar Multiset en lugar de Map .

5. Simultaneidad

Cuando queremos usar Multiset en un entorno concurrente, podemos usar ConcurrentHashMultiset , que es una implementación Multiset segura para subprocesos .

Sin embargo, debemos tener en cuenta que ser seguro para subprocesos no garantiza la coherencia. El uso de los métodos de agregar o quitar funcionará bien en un entorno de subprocesos múltiples, pero ¿qué pasa si varios subprocesos llaman al método setCount ?

Si usamos el método setCount , el resultado final dependería del orden de ejecución en los subprocesos , que no necesariamente se puede predecir. Los métodos de agregar y quitar son incrementales, y ConcurrentHashMultiset puede proteger su comportamiento. Establecer el recuento directamente no es incremental y, por lo tanto, puede causar resultados inesperados cuando se usa al mismo tiempo.

Sin embargo, hay otro tipo de método setCount que actualiza el recuento solo si su valor actual coincide con el argumento pasado. El método devuelve verdadero si la operación se realizó correctamente, una forma de bloqueo optimista:

Multiset bookStore = HashMultiset.create(); // updates the count to 2 if current count is 0 assertThat(bookStore.setCount("Potter", 0, 2)).isTrue(); // updates the count to 5 if the current value is 50 assertThat(bookStore.setCount("Potter", 50, 5)).isFalse();

Si queremos usar el método setCount en código concurrente, debemos usar la versión anterior para garantizar la coherencia. Un cliente de subprocesos múltiples podría realizar un reintento si no se pudo cambiar el recuento.

6. Conclusión

En este breve tutorial, discutimos cuándo y cómo usar un Multiset, lo comparamos con un Mapa estándar y analizamos la mejor manera de usarlo en una aplicación concurrente.

Como siempre, el código fuente de los ejemplos se puede encontrar en GitHub.