Eliminar elementos de las colecciones de Java

Resumen

En este rápido tutorial, vamos a hablar de cuatro formas diferentes de eliminar elementos de las colecciones de Java que coincidan con ciertos predicados.

Naturalmente, también veremos algunas de las advertencias.

Definiendo nuestra colección

Primero, vamos a ilustrar dos enfoques que mutan la estructura de datos original. Luego hablaremos de otras dos opciones que en lugar de eliminar los elementos, crearán una copia de la Colección original sin ellos.

Utilicemos la siguiente colección a lo largo de nuestros ejemplos para demostrar cómo podemos conseguir el mismo resultado utilizando diferentes métodos:

Collection<String> names = new ArrayList<>();names.add("John");names.add("Ana");names.add("Mary");names.add("Anthony");names.add("Mark");

Eliminar elementos con Iterator

El Iterator de Java nos permite tanto recorrer como eliminar cada elemento individual dentro de una Colección.

Para ello, primero necesitamos recuperar un iterador sobre sus elementos utilizando el método iterator. Después, podemos visitar cada elemento con la ayuda de next y eliminarlos usando remove:

Iterator<String> i = names.iterator();while(i.hasNext()) { String e = i.next(); if (e.startsWith("A")) { i.remove(); }}

A pesar de su simplicidad, hay algunas advertencias que debemos tener en cuenta:

  • Dependiendo de la colección podemos encontrarnos con excepciones ConcurrentModificationException
  • Necesitamos iterar sobre los elementos antes de poder eliminarlos
  • Dependiendo de la colección, remove puede comportarse de forma diferente a la esperada. Ej: ArrayList.Iterator elimina el elemento de la colección y desplaza los datos posteriores a la izquierda mientras que, LinkedList.Iterator simplemente ajusta el puntero al siguiente elemento. Así, LinkedList.Iterator funciona mucho mejor que ArrayList.Iterator a la hora de eliminar elementos

Java 8 y Collection.removeIf()

Java 8 introdujo un nuevo método en la interfaz de Collection que proporciona una forma más concisa de eliminar elementos utilizando Predicate:

names.removeIf(e -> e.startsWith("A"));

Es importante señalar que, al contrario que el enfoque Iterator, removeIf funciona de forma similar tanto en LinkedList como en ArrayList.

En Java 8, ArrayList anula la implementación por defecto -que se basa en Iterator- e implementa una estrategia diferente: primero, itera sobre los elementos y marca los que coinciden con nuestro Predicado; después, itera una segunda vez para eliminar (y desplazar) los elementos que fueron marcados en la primera iteración.

Java 8 y la introducción de Stream

Una de las nuevas características principales de Java 8 fue la adición de Stream (y Collectors). Hay muchas formas de crear un Stream a partir de una fuente. Sin embargo, la mayoría de las operaciones que afectan a la instancia de Stream no van a mutar su fuente, sino que la API se centra en crear copias de una fuente y realizar en ellas cualquier operación que podamos necesitar.

Veamos cómo podemos utilizar Stream y Collectors para encontrar/filtrar elementos que coincidan, y no coincidan, con nuestro Predicate.

5.1. Eliminación de elementos con Stream

La eliminación, o mejor dicho, el filtrado de elementos mediante Stream es bastante sencillo, sólo tenemos que crear una instancia de Stream utilizando nuestra Collection, invocar el filtro con nuestro Predicate y luego recoger el resultado con la ayuda de Collectors:

Collection<String> filteredCollection = names .stream() .filter(e -> !e.startsWith("A")) .collect(Collectors.toList());

El Stream es menos invasivo que los enfoques anteriores, favorece el aislamiento y permite la creación de múltiples copias desde el mismo origen. Sin embargo, debemos tener en cuenta que también aumenta la memoria utilizada por nuestra aplicación.

5.2. Collectors.partitioningBy

Combinar tanto Stream.filter como Collectors es bastante práctico, aunque podemos encontrarnos con escenarios en los que necesitemos elementos coincidentes y no coincidentes. En estos casos podemos aprovechar Collectors.partitioningBy:

Map<Boolean, List<String>> classifiedElements = names .stream() .collect(Collectors.partitioningBy((String e) -> !e.startsWith("A")));String matching = String.join(",", classifiedElements.get(true));String nonMatching = String.join(",", classifiedElements.get(false));

Este método devuelve un Map que sólo contiene dos claves, true y false, cada una de las cuales apunta a una lista que contiene los elementos coincidentes, y no coincidentes, respectivamente.

Conclusión

En este artículo, hemos visto algunos métodos para eliminar elementos de Collections y algunas de sus advertencias.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.