Colecciones de Java: la interfaz de lista

Java Collections Framework es un marco fundamental y esencial que cualquier desarrollador de Java fuerte debe conocer como la palma de su mano. Una colección que...

Introducción

El Marco de colecciones de Java es un marco fundamental y esencial que cualquier desarrollador fuerte de Java debe conocer como el dorso de su mano.

Una Colección en Java se define como un grupo o colección de objetos individuales que actúan como un solo objeto.

Hay muchas clases de colección en Java y todas ellas amplían las interfaces java.util.Collection y java.util.Map. Estas clases en su mayoría ofrecen diferentes formas de formular una colección de objetos dentro de un solo objeto.

Java Collections es un marco que proporciona numerosas operaciones sobre una colección: búsqueda, clasificación, inserción, manipulación, eliminación, etc.

Esta es la primera parte de una serie de artículos de colecciones de Java:

Problemas con matrices

arreglos son una de las primeras cosas que se presenta a un nuevo desarrollador de Java horneado.

Una matriz de objetos, muy similar a una colección, representa un grupo de objetos como un solo objeto.

Tanto una matriz como una colección son un objeto que representa a muchos otros objetos, entonces, ¿por qué se necesitan ambos?

Consideremos una colección de productos:

1
2
Product door = new Product("Wooden Door", 35);
Product floorPanel = new Product("Floor Panel", 25);

Tenemos una puerta de madera y un panel de puerta, con un peso de 35 kg y 25 kg respectivamente. Estos son POJO, lo que significa que solo tienen un par de métodos getter y setter y un método toString().

Con esto, es bastante simple instanciar una matriz de estos objetos:

1
Product[] products = { door, floorPanel };

Imprimiendo matrices

Hay muchas razones por las que alguien querría imprimir una matriz, incluida la depuración o la devolución de los resultados:

1
System.out.println(products);

Sin embargo, cuando tratamos de imprimirlo, nos encontramos con un resultado que no es muy amigable para los humanos:

1
com.demo.collections.Product;@14ae5a5

De hecho, necesitamos confiar en la clase auxiliar java.util.Arrays para obtener un resultado sensato:

1
System.out.println(Arrays.toString(products));

Esta vez, estamos viendo algo que tiene más sentido:

1
[Product{name="Wooden Door", weight=35}, Product{name="Floor Panel", weight=25}]

Agregar y quitar elementos {#agregar y quitar elementos}

Nuestra colección de productos se hizo más grande y se supone que debemos agregar una ventana a la matriz:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
final Product window = new Product("Window", 15);
products = add(window, products);
System.out.println(Arrays.toString(products));


public static Object[] add(Object[] array, Object... elements) {
    Object[] tempArray = new Object[array.length + elements.length];
    System.arrayCopy(array, 0, tempArray, 0, array.length);

    for(int i = 0; i < elements.length; i++) {
        tempArray[array.length+i] = elements[i];
        return tempArray;
    }
}

Este es exactamente el tipo de situación en la que probablemente preferirías pegarte un tiro en la pierna, porque las matrices no cambian de tamaño.

Para agregar un elemento, tenemos que hacer una copia de la matriz en una nueva matriz, instanciarla con los nuevos elementos y asignar la nueva matriz a nuestra variable de referencia.

Las matrices son una construcción de bajo nivel y no nos brindan muchas funciones, mientras que las Colecciones están hechas para combatir ese mismo problema y ofrecen muchas funciones y una gran funcionalidad.

Colecciones

Java Collections Framework se envía con el propio JDK. Vale la pena recordar que en los viejos tiempos, especialmente para las personas que escribían código C, a los desarrolladores no se les presentaban estructuras de datos para elegir. De hecho, la gente solía escribir sus propias estructuras de datos, lo que algunos hacen incluso hoy.

Existen razones legítimas de rendimiento por las que alguien podría encontrar una estructura de datos personalizada excelente para un proyecto específico. Pero, para la mayoría de los desarrolladores, confiar en el marco existente es una buena opción.

Java se utiliza para crear sistemas y aplicaciones grandes y complejos. Dicho esto, casi todas las aplicaciones Java terminarán usando el marco de colecciones en un momento u otro.

Todas las clases de colección tienen una estructura de datos subyacente que están implementando: Árboles, HashTables, HashMaps, Colas, etc. Implementar estas estructuras de datos usted mismo, aunque es potencialmente divertido, puede ser muy difícil: hay muchos rincones que tienes que acertar. No hay necesidad de reinventar la rueda si ya te ha servido a menos que desees practicar y desafiarte a ti mismo para encontrar soluciones innovadoras y alternativas.

Echaremos un vistazo a algunos tipos diferentes de colecciones en Java:

  • Listas - Una colección secuencial (ordenada). Realizan un seguimiento de las posiciones de todos los elementos, como matrices y ofrecen operaciones de búsqueda, iteración y vista de rango de sus elementos. Las listas pueden tener elementos duplicados.
  • Conjuntos - Aplica restricciones de exclusividad - no puede contener elementos duplicados. No se preocupa por el orden de iteración dentro de sí mismo, ya que modela la abstracción del conjunto matemático. Los conjuntos no ofrecen ninguna funcionalidad adicional aparte de la heredada de Collections.
  • Colas - Introducir orden de modificación, es decir, si añades elementos en un orden determinado, tienes que seguir un orden determinado. Las colas ofrecen operaciones adicionales de inserción, extracción e inspección sobre sus elementos. Es único que las colas sigan la estructura FIFO (primero en entrar, primero en salir).
  • Deques: similares a las colas, las colas de dos extremos (abreviadas como deques) ofrecen además la posibilidad de realizar operaciones en elementos de ambos lados de la cola.
  • Mapas - Aunque las implementaciones de java.util.Map no se consideran "colecciones verdaderas", ofrecen operaciones de vista de colección que prácticamente les permiten la manipulación a nivel de colección. Esta colección no es una colección de valores individuales, sino pares. Estas son asociaciones entre claves únicas y valores (mapas) que se pueden consultar a partir de esas claves. Es importante tener en cuenta que las claves son únicas y cada clave está asociada con un valor, pero un valor puede estar asociado con más de una clave.

Interfaz Colección

Como se mencionó anteriormente, todas las interfaces de colección dentro de la API de Java amplían una interfaz común: java.util.Collection. Esta interfaz principal proporciona toda la funcionalidad de colecciones comunes.

Cada subinterfaz tiene varias implementaciones, y algunas de estas subinterfaces ofrecen operaciones adicionales:

coleccion_de_colecciones

El punto clave a entender es que cada interfaz define el comportamiento y las características funcionales donde podemos utilizar múltiples estructuras de datos mientras que las implementaciones definen las características de rendimiento, utilizan una estructura de datos específica y son instanciables.

Los métodos más utilizados en la interfaz Colección son:


Nombre del método Descripción del método size() Obtener el número de elementos en la Colección isEmpty() Verdadero si tamaño() == 0, falso en caso contrario add(elemento) Añade el elemento al principio de esta colección addAll(colección) Agrega todos los elementos de la colección de argumentos a esta colección remove(elemento) Elimina el elemento de esta colección removeAll(colección) Elimina todos los elementos de la colección de argumentos a esta colección retainAll() Elimina todos los elementos de esta colección que no están en la colección de argumentos contiene (elemento) Verdadero si el elemento está en esta colección, falso de lo contrario contieneTodo(colección) Verdadero si todos los elementos de la colección de argumentos están en esta colección clear() Eliminar todos los elementos de esta colección


Listas

La primera interfaz, y probablemente la más utilizada, java.util.List.

Cada elemento dentro de la lista tiene un índice, un valor int que define su posición. El recuento de indexación comienza en 0, al igual que la indexación que podemos encontrar con las matrices.

La interfaz java.util.List también agrega un par de otras operaciones más allá de las operaciones de recopilación comunes habituales:

  • obtener(índice int)
  • set(índice int, Objeto objeto)

Estas operaciones se explican por sí mismas y no necesitan mayor explicación. Sin embargo, echemos un vistazo a algunos ejemplos de código.

Adición de un elemento

Usando el método add(), podemos agregar fácilmente objetos a nuestra lista:

1
2
3
4
5
List<String> products = new ArrayList<>();
products.add("Mug");
products.add("Wallet");
products.add("Phone");
System.out.println(products);

Producción:

1
[Mug, Wallet, Phone]

Nota: Estamos instanciando la lista como su implementación concreta ArrayList. En la mayoría de los casos, usaríamos esta implementación para una Lista.

Otra nota: puede especificar el tamaño inicial de ArrayList a través del constructor para evitar cambiar el tamaño si conoce un tamaño definitivo.

La interfaz también proporciona otra versión del método add(), incluido un índice. En este caso, agregamos el elemento al índice dado, y si el índice ya está ocupado por otro elemento, todos los elementos después del agregado se desplazan a la derecha en uno:

1
2
products.add(2, "Pen");
System.out.println(products);

Producción:

1
[Mug, Wallet, Pen, Phone]

Recuperando Elementos

Usando el método get() con el índice dado, podemos recuperar un elemento específico en la lista:

1
System.out.println(products.get(0));

Producción:

1
[Mug]

Eliminación de elementos

Usando el método remove(), podemos eliminar un elemento de la lista. Llamar a este método devolverá el elemento y desplazará los elementos después de él un índice hacia atrás, para llenar el hueco que ahora existe en la secuencia:

1
System.out.println(products.remove(1));

Producción:

1
[Wallet]

Elementos de configuración

Usando el método set(), podemos reemplazar un elemento existente dado un índice:

1
2
3
products.set(1, "Book");

System.out.println(products);

Producción:

1
[Mug, Book, Phone]

Búsqueda de elementos

Usando el método indexOf(), también podemos buscar valores, dado un índice. Si la búsqueda falla y no existe ningún objeto con el índice dado, la lista devolverá -1. En el caso de múltiples objetos iguales, la lista devolverá solo el primer índice.

El uso de lastIndexOf() devolverá el último índice del elemento dado.

1
System.out.println(products.indexOf(5));

Producción:

1
-1

Elementos de iteración {#elementos de iteración}

Aunque es posible iterar con bucles for y enhanced-for, la interfaz proporciona dos nuevas clases auxiliares que nos permiten iterar a través de listas: Iterator y ListIterator:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
for (Iterator<E> iterator = list.iterator(); iterator.hasNext(); ) {
    E element = iterator.next();
    element.someMethod();
    iterator.remove(element);
    //...
}

for (ListIterator<E> iterator = list.listIterator(); iterator.hasNext(); ) {
    E element = iterator.next();
    element.someMethod();
    iterator.remove(element);
    //...
}

Nota: ListIterator ofrece más control sobre la iteración de la lista, ya que permite el recorrido en ambas direcciones, mientras que Iterator solo permite el recorrido en una dirección.

Además, Java 8 nos presenta una forma muy sencilla de imprimir los elementos utilizando una referencia de método:

1
list.forEach(System.out::println);

Implementaciones y diferencias {#implementaciones y diferencias}

ArrayList: implementa java.util.List como una matriz de cambio de tamaño dinámico:

  • Buena implementación de propósito general.
  • Usado por defecto
  • Más caché de CPU comprensivo

LinkedList: implementa java.util.List como una lista doblemente enlazada:

  • Peor rendimiento para muchas operaciones.
  • Usar al agregar elementos al principio
  • Úselo al agregar/eliminar mucho

En términos generales, ArrayList se usa mucho más comúnmente que LinkedList. Y para citar a Joshua Bloch, el hombre que escribió LinkedList:

"¿Alguien realmente usa LinkedList? Lo escribí y nunca lo uso."

Comparación de rendimiento {#comparación de rendimiento}

performance_comparison_arraylist_vs_linkedlist

Debido a sus diferentes naturalezas, estas implementaciones tienen diferentes enfoques y tiempos de ejecución de métodos.

Dependiendo de los requisitos, tendrás que elegir cuál usar. En términos generales, debido a su naturaleza de doble enlace, LinkedList es bueno para añadir y quitar con frecuencia, mientras que ArrayList es bueno para buscar debido al acceso aleatorio.

Conclusión

El marco Java Collections es un marco fundamental que todo desarrollador de Java debería saber cómo usar.

En el artículo, hemos hablado sobre las colecciones en general, los problemas con los arreglos y cómo el framework los combate. Luego, saltamos a las implementaciones de esta interfaz, sus ventajas y desventajas, así como las operaciones que seguramente utilizará en un momento u otro.

If you're interested in reading more about the collection interfaces, continue reading - Colecciones de Java: la interfaz Set.

Licensed under CC BY-NC-SA 4.0