Las listas son estructuras de datos comúnmente utilizadas en todos los lenguajes de programación. En Java, las listas son fundamentales ya que pueden almacenar y organizar elementos de manera secuencial. Además, ofrecen una mayor flexibilidad en la gestión de los datos, ya que pueden cambiar de tamaño de manera dinámica, a diferencia de los arrays tradicionales.
Una lista en Java es una interfaz que pertenece a la biblioteca java.util, y hay muchos tipos de listas que implementan esta interfaz. Las listas son colecciones ordenadas de objetos, similares a las secuencias en matemáticas. Pueden tener elementos duplicados y nulos, y, como todos los objetos en Java, se almacenan en las pilas. Las listas de Java también son indispensables en algoritmos y estructuras de datos, utilizándose en la implementación de algoritmos de ordenación, búsqueda o estructuras de pilas y colas.
La Clase ArrayList en Java
Dentro de las listas en Java, la clase ArrayList es la implementación más utilizada. Es una clase que permite almacenar datos en memoria de forma similar a los Arrays, con la ventaja de que el número de elementos que almacena lo hace de forma dinámica, es decir, no es necesario declarar su tamaño fijo. ArrayList es básicamente un arreglo de tamaño variable y brinda muchos métodos útiles.
Un aspecto a tener muy presente es que hablamos de colecciones de objetos. Por tanto, un ArrayList no puede ser una lista de enteros como tipo primitivo (int), pero sí de objetos Integer. Esto se debe a que las listas solo pueden contener objetos, no tipos primitivos. Por lo tanto, debemos usar envoltorios de objetos, como la clase Integer en lugar de int, Double en lugar de double, y así sucesivamente.
Clases Genéricas o Parametrizadas
Mientras que para declarar un String o una Persona no necesitamos nada más que el nombre de la clase, para declarar un ArrayList hemos de usar además un parámetro especificado entre los símbolos < y >. Solo podemos añadir objetos del tipo declarado al parametrizar la clase. Este tipo de clases, que requieren un tipo como parámetro, se denominan “clases genéricas o parametrizadas”.
Lea también: IVA 21% Excel
En la documentación del API de Java, que una clase está parametrizada se refleja en su documentación. Por ejemplo, si consultamos la documentación de ArrayList veremos que en cabecera aparece como Class ArrayList<E>, lo que nos indica que se requiere un parámetro para crear objetos de tipo ArrayList. Dado que ArrayList es una clase que puede definir una lista de distintos tipos de objeto (como Strings, Personas, Depósitos…), decimos que la clase ArrayList define potencialmente muchos tipos, puesto que con ella podemos crear listas de cualquier tipo de objetos.
Declaración e Inicialización de un ArrayList
Si consultas la documentación de la clase, verás que la clase ArrayList tiene varios constructores. Hay muchas formas de crear una ArrayList, las tres más comunes son:
- Creación directa: Creando el objeto a partir de la clase ArrayList concreta, especificando ArrayList en el lado izquierdo de la asignación. Por ejemplo:
ArrayList<String> listaNombres = new ArrayList<>(); - Uso de polimorfismo: Haciendo uso de la interfaz List en el lado izquierdo. Esto hace que la asignación se acople libremente con la clase ArrayList y nos permite asignar otros tipos de listas y cambiar fácilmente a una implementación de Lista diferente. Por ejemplo:
List<String> listaNombres = new ArrayList<>(); - Uso de la palabra clave var (Java 10+): Para crear variables locales haciendo uso de la palabra clave var. Por ejemplo:
var listaNombres = new ArrayList<String>();
En el caso del constructor sin parámetros, crea una lista ArrayList vacía con una capacidad inicial para diez objetos. La capacidad es modificable luego y se amplía automáticamente a medida que vamos añadiendo elementos.
También podemos especificar la capacidad inicial de la lista. Esto es útil porque cada vez que la lista se llena e intenta agregar otro elemento, la lista actual se copia en una nueva lista con el doble de capacidad que la lista anterior. Esta operación hace que la complejidad sea O(n), por lo que si sabes que vas a almacenar más elementos, debes especificar la capacidad inicial.
Operaciones Básicas con Listas
Las listas en Java incluyen métodos para agregar, eliminar, acceder y modificar elementos. Todas estas operaciones son fundamentales para poder manipular las listas de manera efectiva y eficiente.
Lea también: Guía IVA reducido
-
Añadir elementos: Para agregar elementos a la lista podemos usar el método add(). La complejidad de esta operación en ArrayList es O(1) ya que utiliza un arreglo de acceso aleatorio regular en segundo plano. También podemos añadir un objeto en una posición determinada, no solo al final de la lista.
Por ejemplo, podemos declarar un ArrayList de Strings y rellenarlo con el método add(). También podemos añadir un nuevo elemento en una posición específica con el método add(posición, elemento).
-
Recuperar y acceder a elementos: El número de elementos en la colección lo podemos obtener en cualquier momento utilizando el método size(). Las colecciones como ArrayList tienen una numeración implícita de cada uno de los objetos que la componen, que va desde cero hasta (número de elementos -1).
-
Eliminar elementos: Para eliminar un elemento de ArrayList, se utiliza el método remove(). Se puede eliminar el elemento en un índice específico o bien eliminar la primera ocurrencia de un objeto que se le pasa como parámetro. Ten en cuenta que al eliminar un objeto de la colección, todos los elementos posteriores se renumeran disminuyendo su índice una posición, automáticamente.
-
Recorrer listas: Los Iteradores (Iterator) son muy importantes a la hora de trabajar con los ArrayList, ya que sirven para recorrerlos y trabajar con sus elementos. ListIterator es otro objeto que se apropia de los elementos de una colección y tiene funcionalidades que permiten recorrer dicha colección o lista en ambas direcciones.
Lea también: ¿Cómo localizar tus XML del SAT?
Es posible imprimir las listas directamente (a diferencia de los arreglos regulares) porque anulan el método toString().
Métodos Útiles Adicionales en Java
Existen otros métodos y atajos útiles que se utilizan con frecuencia al trabajar con listas:
-
Crear una lista con elementos iniciales: Es posible crear y completar la lista con algunos elementos en una sola línea. Sin embargo, debes tener cuidado al usar métodos como Arrays.asList(), ya que devuelve una lista inmutable. Si intentas agregar o eliminar elementos después de crear el objeto, obtendrás una excepción UnsupportedOperationException. El método List.of(), disponible desde Java 9, también devuelve una lista inmutable. Sin embargo, podemos pasarla al constructor ArrayList para crear una lista mutable con esos elementos y así poder agregar y quitar elementos sin problema.
-
Crear una lista con N copias de un elemento: Java proporciona un método llamado Collections.nCopies() que es especialmente útil para la evaluación comparativa.
-
Copiar una lista a un arreglo: A veces se necesita convertir una lista en un arreglo para pasarla a un método que acepte este último. Para esto, se puede usar el método toArray(), pasándole un arreglo vacío del tipo deseado, y el método lo devuelve lleno con los elementos de la lista.
-
Ordenar una lista: Para ordenar una lista, podemos usar el método sort() de la clase Collections.
Uso de ArrayList con Objetos Personalizados
Podemos almacenar objetos de clases personalizadas en un ArrayList. Por ejemplo, podríamos crear una clase llamada "PartidoFutbol" y luego crear objetos de esa clase para almacenarlos en un ArrayList. Al utilizar el ArrayList, no importa el número de objetos que haya, ya que los ArrayList son dinámicos.
Se puede leer desde un fichero de texto, una serie de partidos de fútbol y guardarlos en un ArrayList de objetos PartidoFutbol. Luego, se puede recorrer el ArrayList con un Iterator y mostrar los resultados, o incluso eliminar partidos que no cumplan ciertas condiciones, como aquellos que no tengan como resultado un empate.
Listas Bidimensionales en Java
Es posible crear listas bidimensionales, similares a los arreglos 2D. Para ello, se usa una sintaxis como ArrayList<ArrayList<Integer>> listOfLists = new ArrayList<>();, que crea una lista de listas, donde cada lista interna almacenará números enteros. Es importante recordar que las listas internas también deben inicializarse.
Una vez inicializadas las listas internas, se pueden añadir elementos. Para agregar un elemento, primero debemos obtener la referencia a la lista interna específica y luego añadir el elemento a esa lista.
Cómo Elegir Qué Tipo de Lista Usar
En Java no existe un solo tipo de lista, sino que hay variedad para dar respuesta a las necesidades particulares que pueda tener cada desarrollo. Vamos a comparar ArrayList, LinkedList y Vector para ayudarte a elegir cuál es mejor para tu caso de uso.
Comparación de Rendimiento
La siguiente tabla resume la complejidad de tiempo de las operaciones más comunes:
| Operación | Complejidad Típica ArrayList | Complejidad Típica LinkedList |
|---|---|---|
| Obtener elemento (get) | O(1) | O(n) |
| Añadir al final (add) | O(1) | O(1) |
| Añadir en medio | O(n) | O(1) |
| Eliminar por índice | O(n) | O(1) |
| Buscar y Eliminar | O(n) | O(n) |
En general, la operación get (obtener) es mucho más rápida en ArrayList, pero add (agregar) y remove (eliminar) son más rápidos en LinkedList, especialmente cuando se trata de insertar o eliminar elementos en el medio de la lista.
Elegir estructuras de datos es una tarea compleja. Si planeas hacer más operaciones de obtener y agregar que no sean eliminar, usa ArrayList, ya que la operación de obtener es demasiado costosa en LinkedList. Si vas a eliminar elementos y/o insertarlos en el medio con frecuencia, puedes considerar cambiar a LinkedList porque estas operaciones son costosas en ArrayList. Ten en cuenta que si accedes a los elementos secuencialmente (con un iterador), no experimentarás una pérdida de rendimiento con LinkedList mientras obtienes elementos.
ArrayList vs. Vector
Vector es muy similar a ArrayList, pero sus métodos tienen la palabra clave sincronizada, lo que garantiza la seguridad de subprocesos, mientras que ArrayList no. En un programa de subproceso único, es mejor quedarse con ArrayList porque la seguridad de subprocesos tiene un costo de rendimiento. Sin embargo, en la programación de subprocesos múltiples, podrías preferir Vector o usar ArrayList y manejar la sincronización tú mismo.
