Java List: Guía completa de uso, ejemplos y diferencias con Array

目次

1. Introducción

¿Por qué son importantes las List en Java?

En la programación con Java, una List es una de las estructuras de datos que más se utilizan. En escenarios donde se necesita manejar múltiples valores de forma organizada, las List ofrecen una alternativa más flexible y práctica que los arrays tradicionales, por lo que resultan fundamentales en entornos de desarrollo reales.

La List es una interfaz central dentro del Java Collections Framework, y gracias a implementaciones como ArrayList o LinkedList, permite adaptarse a diferentes necesidades. Además, facilita operaciones intuitivas como añadir, eliminar, buscar o actualizar datos, lo que explica su popularidad.

Objetivo y público de este artículo

En este artículo, explicaremos de forma clara y estructurada el uso de Java List desde lo básico hasta aplicaciones prácticas, pensado especialmente para principiantes. Está dirigido a personas como:

  • Quienes recién empiezan a aprender Java y aún no dominan el uso de List.
  • Quienes desean comprender con precisión las diferencias entre arrays y List.
  • Quienes no saben cuándo conviene usar ArrayList o LinkedList.
  • Desarrolladores que buscan repasar los fundamentos de List para aplicarlos en proyectos profesionales.

Al finalizar la lectura, tendrás un entendimiento sólido sobre qué es una List en Java, cómo se implementa y cómo manipular sus elementos con confianza.

En el próximo capítulo comenzaremos con lo más esencial: ¿qué es una List?

2. ¿Qué es una List?

Definición y características principales

En Java, una List es una interfaz que representa una colección ordenada de elementos. Sus dos rasgos más importantes son:

  • Respeta el orden de inserción: los elementos se mantienen en el mismo orden en que fueron añadidos.
  • Acceso mediante índice: permite obtener, modificar o eliminar elementos utilizando un índice que empieza en 0.

Como parte del Collections Framework, proporciona funcionalidades como:

  • Permitir elementos duplicados.
  • Obtener, actualizar o eliminar datos mediante índices.
  • Aumentar o reducir dinámicamente su tamaño (a diferencia de los arrays de longitud fija).

Gracias a estas propiedades, las List son ampliamente utilizadas en proyectos de software profesionales.

Diferencias con los Arrays

En Java, los arrays (int[], String[], etc.) también permiten almacenar múltiples valores, pero existen diferencias clave respecto a las List.

AspectoArrayList
Modificación del tamañoNo (longitud fija)Sí (dinámico)
Funciones disponiblesBásicas (acceso por índice, longitud)Métodos avanzados (add, remove, contains, etc.)
Tipos soportadosIncluye tipos primitivosSolo objetos (se requieren wrapper classes)
Seguridad de tiposChequeo en compilaciónUso de generics para mayor precisión

En resumen, una List ofrece mayor flexibilidad y funcionalidades, por lo que suele ser más práctica que los arrays en la mayoría de los casos.

List e implementaciones más comunes

Para usar una List en Java, normalmente se declara con la interfaz List y se instancia con una clase concreta. Entre las más utilizadas están:

  • ArrayList
    Basada en arrays dinámicos, con acceso muy rápido por índice. Ideal para búsquedas y accesos aleatorios.
  • LinkedList
    Basada en nodos enlazados, optimizada para inserciones y eliminaciones frecuentes.
  • Vector
    Similar a ArrayList, pero sincronizada para hilos. Hoy en día se usa poco por su menor rendimiento.

En la mayoría de los proyectos, ArrayList suele ser la opción recomendada, salvo que se requieran características específicas de otras implementaciones.

3. Uso básico de una List

A continuación, explicaremos paso a paso cómo trabajar con List en Java. Para los ejemplos usaremos principalmente ArrayList, que es la implementación más habitual.

Declaración e inicialización de una List

La forma más común de declarar una List es utilizando la interfaz List y crear una instancia de ArrayList:

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> fruits = new ArrayList<>();
    }
}

Con generics se especifica el tipo de dato que almacenará la lista (en este caso String).

Añadir elementos (add)

Para agregar elementos se usa el método add():

fruits.add("Manzana");
fruits.add("Banana");
fruits.add("Mandarina");

Los elementos se insertan manteniendo el orden de inserción.

Obtener elementos (get)

El método get(int index) devuelve el elemento en la posición indicada:

System.out.println(fruits.get(0)); // Imprime "Manzana"

Recuerda que los índices comienzan en 0.

Actualizar elementos (set)

Con set(int index, E element) puedes reemplazar un elemento en una posición específica:

fruits.set(1, "Uva"); // Reemplaza "Banana" por "Uva"

Eliminar elementos (remove)

Puedes eliminar elementos tanto por índice como por valor:

fruits.remove(0);          // Elimina el primer elemento
fruits.remove("Mandarina"); // Elimina la primera coincidencia de "Mandarina"

Obtener el tamaño (size)

El número actual de elementos se obtiene con size():

System.out.println(fruits.size()); // Ejemplo: devuelve 2

Comprobar existencia (contains)

Para verificar si un elemento existe, se usa contains():

if (fruits.contains("Uva")) {
    System.out.println("La lista contiene Uva");
}

Resumen de operaciones básicas

OperaciónMétodoDescripción
Añadiradd("Elemento")Inserta al final de la lista
Obtenerget(índice)Accede al elemento por índice
Actualizarset(índice, nuevoElemento)Reemplaza un valor en una posición
Eliminarremove(índice/elemento)Borra un elemento
Tamañosize()Devuelve el número de elementos
Existenciacontains("Elemento")Verifica si existe un valor

4. Ejemplos de iteración con List

En esta sección veremos diferentes formas de recorrer una lista: for clásico, for-each y Stream API.

Recorrido con bucle for

List<String> fruits = new ArrayList<>();
fruits.add("Manzana");
fruits.add("Banana");
fruits.add("Mandarina");

for (int i = 0; i < fruits.size(); i++) {
    System.out.println(fruits.get(i));
}

Este método permite un control preciso con índices, útil cuando se requiere acceder a posiciones concretas.

Recorrido con for-each

for (String fruit : fruits) {
    System.out.println(fruit);
}

Es más legible y es el método más usado para recorrer colecciones.

Recorrido con Stream API

fruits.stream().forEach(fruit -> System.out.println(fruit));

Además, con filter() podemos aplicar condiciones:

fruits.stream()
      .filter(fruit -> fruit.contains("n"))
      .forEach(System.out::println);
MétodoVentajaUso recomendado
for clásicoPermite control con índicesProcesos que dependen de la posición
for-eachMás simple y legibleRecorrido secuencial sin lógica extra
Stream APIPotente para filtros y transformacionesProcesamiento funcional de datos

5. Diferencias y usos de ArrayList y LinkedList

Dentro de las implementaciones más comunes de la interfaz List encontramos ArrayList y LinkedList. Aunque ambas cumplen la misma interfaz, su rendimiento y estructura interna son distintos, por lo que elegir la correcta depende del caso de uso.

Características de ArrayList

ArrayList está basado en un array dinámico (redimensionable):

  • Acceso aleatorio muy rápido (get() es O(1)).
  • Añadir al final es eficiente (tiempo promedio O(1)).
  • Insertar o eliminar en posiciones intermedias es más costoso (O(n)).

Usos recomendados:

  • Lectura frecuente y acceso por índice.
  • Cuando se conoce de antemano el tamaño aproximado de la lista.
  • Escenarios con pocas modificaciones.
List<String> list = new ArrayList<>();

Características de LinkedList

LinkedList se implementa como una lista doblemente enlazada:

  • Inserciones y eliminaciones rápidas en cualquier posición (especialmente al inicio y al final).
  • Acceso por índice es más lento (O(n)).
  • Mayor consumo de memoria comparado con ArrayList.

Usos recomendados:

  • Escenarios con muchas inserciones o eliminaciones.
  • Implementación de colas (Queue) o pilas (Stack).
  • Recorridos secuenciales sin necesidad de índice.
List<String> list = new LinkedList<>();

Comparación de rendimiento

En teoría, la complejidad de tiempo (Big-O) de las operaciones más comunes es la siguiente:

OperaciónArrayListLinkedList
get(int index)O(1)O(n)
add(E e) (final)O(1)O(1)
add(int index, E e)O(n)O(n)
remove(int index)O(n)O(n)
IteraciónO(n)O(n)

Nota: el rendimiento real depende del tamaño de los datos y optimizaciones de la JVM.

Puntos prácticos para elegir

  • ArrayList: ideal para colecciones donde prima el acceso rápido por índice.
  • LinkedList: útil cuando se requieren muchas inserciones o eliminaciones en medio de la lista.
  • Siempre conviene realizar benchmarks en casos críticos para decidir con base en datos reales.

6. Usos avanzados de List

Además de las operaciones básicas, una List en Java puede aprovecharse en escenarios más avanzados como ordenamiento, barajado, filtrado y transformación de datos.

Ordenar con Collections.sort

import java.util.*;

List<String> fruits = new ArrayList<>();
fruits.add("Banana");
fruits.add("Manzana");
fruits.add("Mandarina");

Collections.sort(fruits);
System.out.println(fruits); // [Banana, Manzana, Mandarina]

También es posible definir un orden personalizado con Comparator:

fruits.sort(Comparator.reverseOrder()); // Orden descendente

Barajar elementos con Collections.shuffle

Para desordenar aleatoriamente la lista:

Collections.shuffle(fruits);
System.out.println(fruits);

Útil en juegos o para mostrar resultados en orden aleatorio.

Filtrado con Stream API

List<String> filtered = fruits.stream()
    .filter(fruit -> fruit.contains("a"))
    .collect(Collectors.toList());

System.out.println(filtered);

Transformación con map

List<Integer> lengths = fruits.stream()
    .map(String::length)
    .collect(Collectors.toList());

System.out.println(lengths);

Resumen de operaciones avanzadas

OperaciónEjemploUso
OrdenarCollections.sort(list)Orden ascendente
BarajarCollections.shuffle(list)Orden aleatorio
Filtrarstream().filter(...)Extraer elementos por condición
Transformarstream().map(...)Cambiar formato o tipo

7. Errores comunes y cómo solucionarlos

Al trabajar con List en Java, es frecuente que los principiantes enfrenten excepciones (errores en tiempo de ejecución). A continuación, veremos los más habituales, sus causas y formas de resolverlos.

IndexOutOfBoundsException (índice fuera de rango)

Causa:

Ocurre cuando se intenta acceder a un índice inexistente.

List<String> list = new ArrayList<>();
list.add("Manzana");
System.out.println(list.get(1)); // Error: Index 1 out of bounds

Solución:

Verificar siempre el tamaño antes de acceder:

if (list.size() > 1) {
    System.out.println(list.get(1));
}

NullPointerException (referencia nula)

Causa:

Sucede cuando la lista no está inicializada o algún elemento es null.

List<String> list = null;
list.add("Manzana"); // NullPointerException

Solución:

Asegúrate de inicializar la lista antes de usarla:

List<String> list = new ArrayList<>();

O verifica que no sea nula antes de operar:

if (list != null) {
    list.add("Manzana");
}

ConcurrentModificationException

Causa:

Ocurre cuando se modifica la lista directamente mientras se recorre con un for-each o un Iterator.

for (String fruit : list) {
    if (fruit.equals("Banana")) {
        list.remove(fruit); // Lanza ConcurrentModificationException
    }
}

Solución:

Usar un Iterator para eliminar de forma segura:

Iterator<String> it = list.iterator();
while (it.hasNext()) {
    if (it.next().equals("Banana")) {
        it.remove();
    }
}

O con Java 8+, utilizar removeIf():

list.removeIf(fruit -> fruit.equals("Banana"));

Otros puntos a considerar

  • Confirmar siempre que la List no sea null.
  • Recordar que los índices empiezan en 0.
  • No olvidar inicializar la variable antes de su uso.

Resumen de errores y soluciones

ErrorCausaSolución
IndexOutOfBoundsExceptionAcceso a un índice inexistenteComprobar tamaño con size()
NullPointerExceptionLista o elementos en estado nullInicializar y verificar null
ConcurrentModificationExceptionModificar durante un recorridoUsar Iterator o removeIf()

8. Conclusiones

Repaso de los fundamentos

Hemos explorado desde lo básico hasta técnicas más avanzadas con List en Java. Los puntos clave incluyen:

  • Una List es una colección ordenada y que permite duplicados.
  • Sus implementaciones más comunes son ArrayList y LinkedList.
  • Las operaciones principales incluyen añadir, obtener, actualizar y eliminar.
  • Existen múltiples formas de recorrer listas: for, for-each y Stream API.
  • Soporta operaciones avanzadas como ordenar, filtrar o transformar datos.
  • Conocer los errores típicos permite evitarlos de forma proactiva.

ArrayList vs LinkedList

La elección depende del caso de uso:

  • ArrayList: mejor para accesos rápidos y lecturas frecuentes.
  • LinkedList: más eficiente en inserciones y eliminaciones frecuentes.

Siguientes pasos de aprendizaje

Dominar List es solo el comienzo dentro del Collections Framework. Para avanzar, conviene estudiar:

  • Set y Map: colecciones con elementos únicos y pares clave-valor.
  • Clase Collections: utilidades como mínimo, máximo, ordenamientos.
  • Stream API: estilo de programación funcional.
  • Generics: seguridad de tipos en colecciones.

Con una base sólida en List, la programación en Java se vuelve más versátil y eficiente.

Preguntas frecuentes (FAQ)

Q1. ¿Cuál es la diferencia entre List y Array?

A: Los arrays tienen tamaño fijo, mientras que las List son dinámicas y ofrecen métodos adicionales como add, remove o contains.

Q2. ¿Cuándo elegir ArrayList o LinkedList?

A: Usa ArrayList si se realizan muchas lecturas aleatorias, y LinkedList si predominan las inserciones o eliminaciones.

Q3. ¿Se pueden guardar tipos primitivos en una List?

A: No directamente. Se deben usar las wrapper classes como Integer, Double, etc.

List<Integer> numbers = new ArrayList<>();
numbers.add(10); // Autoboxing convierte int a Integer

Q4. ¿Cómo ordenar los elementos?

A: Con Collections.sort(list) para orden ascendente, o un Comparator para un orden personalizado.

Q5. ¿Cómo evitar elementos duplicados?

A: Las List permiten duplicados. Para evitarlos, usa Set (ejemplo: HashSet) o aplica distinct() con Stream:

List<String> distinctList = list.stream()
    .distinct()
    .collect(Collectors.toList());

Q6. ¿Cómo vaciar una List?

A: Con clear():

list.clear();

Q7. ¿Cuáles son las operaciones más usadas?

A: add, get, remove y size son las más comunes en el día a día.