Saltar a contenido

📋 List en Java

En este apartado vamos a estudiar una de las interfaces más importantes del framework de colecciones de Java: List.


1️⃣ ¿Qué es List?

List es una interfaz de Java que representa una secuencia ordenada de elementos.

Eso significa que en una lista:

  • los elementos mantienen un orden
  • cada elemento tiene una posición o índice
  • se pueden guardar elementos duplicados

Ejemplo:

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

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

nombres.add("Ana");
nombres.add("Luis");
nombres.add("Ana");

System.out.println(nombres);

Salida:

[Ana, Luis, Ana]

Observa que:

  • "Ana" aparece dos veces
  • los elementos salen en el mismo orden en el que se añadieron
  • podemos acceder a cada uno por posición

🧠 Idea clave

Una List se parece bastante a un array, pero con ventajas importantes:

  • puede crecer y reducirse dinámicamente
  • tiene muchos métodos útiles ya incorporados
  • permite trabajar de forma más cómoda con grupos de datos

2️⃣ Características principales de List

✅ Mantiene el orden de inserción

Si añades elementos en este orden:

lista.add("rojo");
lista.add("verde");
lista.add("azul");

La lista quedará así:

[rojo, verde, azul]

✅ Permite duplicados

A diferencia de un Set, una List sí permite repetir elementos:

lista.add("Ana");
lista.add("Ana");

Resultado:

[Ana, Ana]

✅ Permite acceso por índice

Igual que en los arrays, cada elemento tiene una posición:

Índice:   0      1       2
Valor:  Ana    Luis   Carlos

Podemos acceder así:

System.out.println(lista.get(1));

Salida:

Luis

✅ Puede almacenar objetos

Una List puede guardar:

  • String
  • Integer
  • Double
  • objetos creados por ti, como Alumno, Producto, Reserva, etc.

Ejemplo:

List<Integer> numeros = new ArrayList<>();
List<Producto> productos = new ArrayList<>();

3️⃣ Implementaciones principales de List

List es una interfaz, así que no se puede instanciar directamente:

❌ Incorrecto:

List<String> lista = new List<>();

✔ Correcto:

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

Las principales clases que implementan List son:

Clase Características principales
ArrayList Lista basada en array dinámico. Muy rápida para acceder por índice.
LinkedList Lista basada en nodos enlazados. Mejor para insertar o eliminar en medio.
Vector Similar a ArrayList pero sincronizado (antiguo, casi no se usa).
Stack Implementa una pila (LIFO). También es una clase antigua basada en Vector.

Pero en este tema nos vamos a centrar sobre todo en las dos más importantes y más usadas:

  • ArrayList
  • LinkedList

4️⃣ ArrayList

Es la implementación de List más utilizada.

ArrayList es una implementación de List basada internamente en un array dinámico.

Es decir:

  • por dentro usa un array
  • pero ese array puede crecer cuando se llena
  • Java se encarga automáticamente de crear uno más grande y copiar los elementos

🧠 ¿Cómo funciona internamente?

De forma simplificada, internamente ocurre algo parecido a esto:

Object[] elementos;

Cuando el ArrayList se llena:

  1. se crea un array más grande
  2. se copian los elementos antiguos
  3. se sigue trabajando con el nuevo array

Eso hace que para el programador sea muy cómodo, porque no tiene que preocuparse por el tamaño.


✅ Ventajas de ArrayList

  • acceso muy rápido por índice
  • muy cómodo de usar
  • ideal cuando hacemos muchas lecturas
  • suele ser la implementación más usada

⚠️ Inconvenientes de ArrayList

  • insertar o borrar en medio puede ser más lento
  • porque hay que desplazar elementos, ya que internamente es un array.

Ejemplo:

[Ana, Luis, Carlos, Marta]

Si borramos "Luis":

[Ana, Carlos, Marta]

Java tiene que mover los elementos posteriores una posición.


💡 Ejemplo básico con ArrayList

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

public class EjemploArrayList {
    public static void main(String[] args) {
        List<String> nombres = new ArrayList<>();

        nombres.add("Ana");
        nombres.add("Luis");
        nombres.add("Carlos");

        System.out.println(nombres);
    }
}

Salida:

[Ana, Luis, Carlos]

5️⃣ LinkedList

LinkedList es una implementación de List basada en una lista enlazada.

En vez de guardar los elementos en posiciones consecutivas de un array, cada elemento se almacena en un nodo (objeto).

Cada nodo contiene:

  • el dato
  • una referencia al nodo siguiente
  • y en Java además, en esta implementación, también una referencia al anterior

Por eso se dice que LinkedList es una lista doblemente enlazada.


🧠 ¿Cómo funciona internamente?

Simplificando mucho, sería algo parecido a esto:

[Ana] <-> [Luis] <-> [Carlos] <-> [Marta]

Cada nodo “sabe” quién va antes y quién va después.

LinkedList


✅ Ventajas de LinkedList

  • insertar o eliminar elementos al principio o en medio puede ser más eficiente que en ArrayList

⚠️ Inconvenientes de LinkedList

  • acceder por índice es más lento
  • ocupa más memoria
  • en muchos casos prácticos, ArrayList sigue siendo mejor elección

Para acceder al elemento de índice 500, por ejemplo, no puede ir directamente: tiene que recorrer nodos hasta llegar a ese.


💡 Ejemplo básico con LinkedList

import java.util.LinkedList;
import java.util.List;

public class EjemploLinkedList {
    public static void main(String[] args) {
        List<String> nombres = new LinkedList<>();

        nombres.add("Ana");
        nombres.add("Luis");
        nombres.add("Carlos");

        System.out.println(nombres);
    }
}

Salida:

[Ana, Luis, Carlos]

6️⃣ ArrayList vs LinkedList

ArrayList

  • Es como una fila de cajas contiguas.
  • Ir a una posición concreta es rapidísimo.
  • Pero meter o quitar algo en medio molesta, porque hay que mover lo que viene detrás.

LinkedList

  • Es como una cadena de nodos enlazados.
  • Insertar o quitar al principio o al final puede ser muy fácil.
  • Pero llegar a una posición concreta cuesta más, porque hay que ir pasando nodo a nodo.

📌 Regla práctica

En la mayoría de ejercicios y programas:

usa ArrayList por defecto

Piensa en LinkedList cuando:

  • hay muchas inserciones y borrados al principio o en posiciones ya localizadas
  • no importa tanto el acceso rápido por índice
  • o quieres reutilizarla luego como cola

7️⃣ Eficiencia de operaciones

📊 Complejidad aproximada

Operación ArrayList LinkedList Por qué
Acceder por índice (get(i)) Muy eficiente O(1) Poco eficiente O(n) En ArrayList se va directo a la posición del array. En LinkedList hay que recorrer nodos.
Modificar por índice (set(i, x)) Muy eficiente O(1) Poco eficiente O(n) Primero hay que localizar la posición; en lista enlazada eso implica recorrer.
Añadir al final (add(x)) Muy eficiente amortizado O(1) Muy eficiente O(1) ArrayList suele tener hueco al final; a veces debe redimensionar O(n). LinkedList enlaza un nuevo nodo al final.
Insertar al principio Poco eficiente O(n) Muy eficiente O(1) En ArrayList hay que desplazar todos los elementos. En LinkedList solo cambia enlaces.
Insertar en medio Poco eficiente O(n) Poco eficiente en general O(n) En ArrayList se desplazan elementos. En LinkedList no se desplazan, pero normalmente hay que recorrer hasta llegar al nodo.
Eliminar al principio Poco eficiente O(n) Muy eficiente O(1) En ArrayList todo se corre una posición. En LinkedList solo se cambia el inicio.
Eliminar al final Muy eficiente O(1) Muy eficiente O(1) En ArrayList se quita el último directamente. En LinkedList también puede ser rápido si guarda referencia al último nodo.
Eliminar en medio Poco eficiente O(n) Poco eficiente en general O(n) ArrayList desplaza elementos; LinkedList necesita localizar el nodo antes de quitarlo.
Recorrer todos los elementos Eficiente O(n) Eficiente O(n) En ambos hay que pasar por todos.
Memoria Menor consumo Mayor consumo LinkedList necesita nodos y referencias extra.

🧠 ¿Qué significa esto?

O(1)

O(1) significa tiempo constante. La operación tarda prácticamente lo mismo aunque la lista sea pequeña o grande.

Ejemplo:

lista.get(5);

En ArrayList es rápido porque va directamente a la posición.


O(n)

La operación depende del número de elementos.

Cuantos más elementos haya, más puede tardar.

Ejemplo:

  • buscar un nombre
  • recorrer hasta una posición
  • comprobar si existe un elemento

🎯 Entender que...

  • ArrayList es muy buena para leer por índice
  • LinkedList no es buena para acceder por posición muchas veces
  • ambas pueden usarse como List, pero no rinden igual

8️⃣ Métodos más usados de List

Ahora vamos a ver los métodos más usados de la interfaz List.


add(elemento)

Añade un elemento al final de la lista.

List<String> nombres = new ArrayList<>();//da igual que sea arrayList que LinkedList

nombres.add("Ana");
nombres.add("Luis");

System.out.println(nombres);

Salida:

[Ana, Luis]

add(indice, elemento)

Inserta un elemento en una posición concreta.

nombres.add(1, "Marta");
System.out.println(nombres);

Salida:

[Ana, Marta, Luis]

👀 get(indice)

Devuelve el elemento de una posición concreta.

System.out.println(nombres.get(0));

Salida:

Ana

✏️ set(indice, elemento)

Modifica el valor de una posición.

nombres.set(1, "Carlos");
System.out.println(nombres);

Salida:

[Ana, Carlos, Luis]

remove(indice)

Elimina el elemento de una posición.

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

Salida:

[Ana, Luis]

remove(objeto)

Elimina la primera aparición de un elemento.

nombres.remove("Ana");
System.out.println(nombres);

Salida:

[Luis]

📏 size()

Devuelve cuántos elementos tiene la lista.

System.out.println(nombres.size());

🔍 contains(objeto)

Comprueba si un elemento existe en la lista.

System.out.println(nombres.contains("Luis"));

Salida:

true

📍 indexOf(objeto)

Devuelve la posición de la primera aparición del elemento.

System.out.println(nombres.indexOf("Luis"));

Si no existe, devuelve:

-1

🧹 clear()

Elimina todos los elementos de la lista.

nombres.clear();
System.out.println(nombres);

Salida:

[]

isEmpty()

Indica si la lista está vacía.

System.out.println(nombres.isEmpty());

9️⃣ Recorrer una List

🔁 Con for clásico

Muy útil si necesitas los índices.

for (int i = 0; i < nombres.size(); i++) {
    System.out.println(i + " -> " + nombres.get(i));
}

🔁 Con for-each

Muy cómodo cuando solo quieres recorrer los elementos.

for (String nombre : nombres) {
    System.out.println(nombre);
}

🔁 Con while

También se puede, aunque se usa menos.

int i = 0;

while (i < nombres.size()) {
    System.out.println(nombres.get(i));
    i++;
}

🔟 Ejemplo completo con List

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

public class EjemploList {
    public static void main(String[] args) {
        List<String> frutas = new ArrayList<>();

        frutas.add("Manzana");
        frutas.add("Pera");
        frutas.add("Plátano");

        System.out.println("Lista inicial: " + frutas);

        frutas.add(1, "Fresa");
        System.out.println("Tras insertar: " + frutas);

        frutas.set(2, "Kiwi");
        System.out.println("Tras modificar: " + frutas);

        frutas.remove("Manzana");
        System.out.println("Tras eliminar: " + frutas);

        System.out.println("Tamaño: " + frutas.size());
        System.out.println("¿Contiene Pera? " + frutas.contains("Pera"));

        for (String fruta : frutas) {
            System.out.println(fruta);
        }
    }
}

1️⃣1️⃣ Listas de objetos

Una de las partes más importantes de List en Java real es que normalmente no guardaremos solo cadenas o enteros, sino objetos.

Ejemplo:

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

public class Principal {
    public static void main(String[] args) {
        List<Alumno> alumnos = new ArrayList<>();

        alumnos.add(new Alumno("Ana", 8));
        alumnos.add(new Alumno("Luis", 6));
        alumnos.add(new Alumno("Marta", 9));

        for (Alumno a : alumnos) {
            System.out.println(a.getNombre() + " - " + a.getNota());
        }
    }
}

Y la clase:

public class Alumno {
    private String nombre;
    private int nota;

    public Alumno(String nombre, int nota) {
        this.nombre = nombre;
        this.nota = nota;
    }

    public String getNombre() {
        return nombre;
    }

    public int getNota() {
        return nota;
    }
}

Esto conecta directamente con POO.


1️⃣2️⃣ Errores típicos con List

⚠️ Confundir size() con length

❌ Incorrecto:

lista.length //no existe

✔ Correcto:

lista.size()

Los arrays usan length, las colecciones usan size().


⚠️ Intentar usar corchetes

❌ Incorrecto:

lista[0]

✔ Correcto:

lista.get(0)

⚠️ Olvidar importar las clases

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

o

import java.util.LinkedList;

⚠️ Pensar que List se puede instanciar

❌ Incorrecto:

List<String> lista = new List<>();

✔ Correcto:

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

⚠️ Acceder a índices fuera de rango

lista.get(10);

Si la lista no tiene esa posición, salta una excepción:

IndexOutOfBoundsException

⚠️ Confundir remove(int) con remove(Object)

Esto da problemas sobre todo con Integer.

Ejemplo:

List<Integer> numeros = new ArrayList<>();
numeros.add(10);
numeros.add(20);
numeros.add(30);

numeros.remove(1);

Esto no elimina el valor 1.
Elimina el elemento de índice 1, es decir, el 20.


1️⃣3️⃣ Buenas prácticas

✅ Programar contra la interfaz

Mejor así:

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

que así:

ArrayList<String> nombres = new ArrayList<>();

¿Por qué?

Porque si luego quieres cambiar la implementación, te resultará más fácil:

List<String> nombres = new LinkedList<>();

✅ Elegir ArrayList por defecto

Si no hay un motivo claro para otra cosa, usa:

new ArrayList<>()

✅ Usar nombres claros

List<Alumno> alumnos = new ArrayList<>();
List<Producto> productos = new ArrayList<>();

🧠 Resumen final

List es una interfaz que representa una colección ordenada de elementos.

Sus características principales son:

  • mantiene el orden
  • permite duplicados
  • permite acceso por índice

Las implementaciones más importantes son:

  • ArrayList → basada en array dinámico, muy usada
  • LinkedList → basada en nodos enlazados

🎯 Idea práctica

  • Usa ArrayList en la mayoría de situaciones
  • Piensa en LinkedList si el problema encaja mejor con inserciones y borrados frecuentes

📊 Métodos más usados de List y eficiencia

Método Qué hace ArrayList LinkedList
add(elemento) Añade al final O(1) amortizado O(1)
add(indice, elemento) Inserta en una posición O(n) O(n)
get(indice) Devuelve el elemento de una posición O(1) O(n)
set(indice, elemento) Modifica el elemento de una posición O(1) O(n)
remove(indice) Elimina el elemento de una posición O(n) O(n)
remove(objeto) Elimina la primera aparición del elemento O(n) O(n)
size() Devuelve cuántos elementos hay O(1) O(1)
isEmpty() Comprueba si está vacía O(1) O(1)
contains(objeto) Comprueba si existe un elemento O(n) O(n)
indexOf(objeto) Devuelve la posición de la primera aparición O(n) O(n)
clear() Elimina todos los elementos O(n) O(n)