Interfaz List en Java
Colecciones ordenadas e indexables en Java con la interfaz List y sus operaciones principales.
List<E> es Collection<E> más dos compromisos adicionales: los elementos tienen un orden definido, y ese orden es accesible mediante un índice entero. Una vez que tienes orden e índice, toda una clase de métodos cobra sentido — get(i), set(i, x), indexOf(x), subList(from, to), ordenar, iterar en reversa. Este capítulo recorre el contrato; las implementaciones (ArrayList, LinkedList, Vector) vienen inmediatamente después con sus propias características de rendimiento.
Qué significa "ordenado" aquí
"Ordenado" en una List significa que el orden de inserción se preserva — el índice 0 es el primer elemento que insertaste, el índice size() - 1 es el último, y agregar un nuevo elemento al final no desplaza nada. No significa "ordenado alfabéticamente" — una lista mantiene el orden que tú produzcas. Si quieres iteración ordenada, debes llamar a Collections.sort(list) (que muta la lista) o usar TreeSet / TreeMap desde el principio. No confundas los dos conceptos.
Se permiten duplicados. [1, 1, 2, 1] es una List<Integer> perfectamente válida.
Los métodos que List añade sobre Collection
Todo lo que Collection declara sigue estando disponible — add, remove, contains, size, etc. List agrega además operaciones posicionales y conscientes del orden:
Acceso posicional
E get(int index)— elemento enindex.E set(int index, E element)— reemplaza y devuelve el valor anterior.void add(int index, E element)— inserta (desplaza los elementos posteriores hacia la derecha).E remove(int index)— elimina por posición (devuelve el elemento eliminado). Nota la sobrecarga conObject—list.remove(1)llama a la versiónint;list.remove(Integer.valueOf(1))llama a la versiónObject.
Búsqueda
int indexOf(Object o)— primera ocurrencia, o-1.int lastIndexOf(Object o)— última ocurrencia, o-1.
Subvistas e iteración
List<E> subList(int fromIndex, int toIndex)— una vista en vivo de un rango. Modificarla modifica la lista subyacente (y viceversa). Semiabierto:[from, to).ListIterator<E> listIterator()/listIterator(int index)— iterador que también puede recorrer hacia atrás, obtener el índice actual y hacerset/adden la posición del cursor. El capítulo ListIterator lo cubre en detalle.
Mutación masiva vinculada al orden
default void replaceAll(UnaryOperator<E> op)— aplicaopa cada elemento en su lugar.default void sort(Comparator<? super E> c)— ordena la lista usandoc(o el orden natural si esnull).boolean addAll(int index, Collection<? extends E> c)— inserta una colección completa enindex.
Fábricas (Java 9+)
List.of(...)— una lista no modificable con los elementos dados. Compacta y sin asignaciones adicionales para tamaños pequeños.List.copyOf(Collection)— una instantánea no modificable de otra colección.
La igualdad en List es sensible al orden
Dos listas son iguales si y solo si tienen el mismo tamaño, en el mismo orden, con elementos iguales en cada índice. List.of(1, 2) no es igual a List.of(2, 1), aunque como Sets sí lo serían. Esa es una regla estricta del contrato de List — si comparas dos listas y obtienes false cuando "no deberías", verifica el orden primero.
subList es una vista, no una copia
Esto confunde a casi todos los aprendices en algún momento:
List<Integer> xs = new ArrayList<>(List.of(0, 1, 2, 3, 4, 5));
List<Integer> middle = xs.subList(2, 5); // [2, 3, 4]
middle.set(0, 99);
System.out.println(xs); // [0, 1, 99, 3, 4, 5] — xs changed!
middle.clear();
System.out.println(xs); // [0, 1, 5] — gone from xssubList devuelve una ventana en vivo. Las lecturas y escrituras pasan a través de la lista subyacente. Eso es muy útil para algoritmos en el lugar — borrar un rango, ordenar un rango, insertar un rango — pero también significa que no puedes mantener una referencia a subList y luego mutar el padre por cualquier otro camino. El Javadoc indica que los cambios estructurales en la lista subyacente fuera de la sublista "indefinen" el comportamiento de la sublista. En la práctica, ConcurrentModificationException en la siguiente llamada.
Si quieres un fragmento independiente, cópialo: new ArrayList<>(xs.subList(2, 5)).
Las dos sobrecargas de remove
Un error común:
List<Integer> nums = new ArrayList<>(List.of(10, 20, 30));
nums.remove(1); // removes index 1 → [10, 30]
nums.remove(Integer.valueOf(10)); // removes the value 10 → [30]La sobrecarga int gana porque int es más específico que Integer. Si quieres "eliminar el valor 10", enciérralo explícitamente. Este es uno de los pocos lugares en el lenguaje donde las reglas de autoboxing y la resolución de sobrecargas entran en conflicto activo.
Ordenar en el lugar
list.sort(comparator) muta la lista. Pasa null para usar el orden natural de los elementos (su Comparable); de lo contrario, pasa un Comparator. Esta es la forma moderna — Collections.sort(list) todavía funciona y es idéntica, pero list.sort(...) es el método predeterminado de List:
List<String> names = new ArrayList<>(List.of("Linus", "Ada", "Grace"));
names.sort(null); // natural: ["Ada", "Grace", "Linus"]
names.sort(Comparator.comparingInt(String::length)); // shortest firstEl capítulo Comparable / Comparator más adelante en esta parte es el manual de referencia sobre qué significa null y cómo construir comparadores para tus propios tipos.
Fábricas inmutables: cuándo add lanza una excepción
List.of(...), List.copyOf(...), y las listas devueltas por Collectors.toUnmodifiableList() son no modificables. Rechazan cada llamada mutante con UnsupportedOperationException. También rechazan elementos null. Son ideales para datos de solo lectura compartidos ampliamente:
List<String> CONSTANTS = List.of("red", "green", "blue");
CONSTANTS.add("yellow"); // throws UnsupportedOperationExceptionSi más adelante podrías querer mutar la lista, empieza con new ArrayList<>(List.of(...)).
Un ejemplo completo: todos los métodos específicos de List
El programa a continuación ejercita los métodos que List añade más allá de Collection. Observa cómo la mutación de subList se propaga, la trampa de la sobrecarga y la diferencia entre sort y replaceAll.
Algunas conclusiones de la salida que debes recordar:
remove(1)eliminó20(el valor en el índice 1);remove(Integer.valueOf(10))eliminó10por valor. Mismo nombre de método, dos trabajos distintos según el tipo estático del argumento.- Después de
mid.clear(), la lista padre es[0, 1, 5]. La vista era ese rango — borrarla eliminó esos elementos del array subyacente. replaceAllmantiene la lista con la misma longitud y reescribe cada elemento en su lugar;sortreorganiza lo que ya hay. Se combinan bien.
Qué sigue
Ahora conoces el contrato de List — qué está garantizado, qué muta qué, dónde están los peligros. Es momento de conocer la implementación que usarás el 90% del tiempo: el ArrayList respaldado por un array redimensionable. El mismo contrato, características de rendimiento específicas y algunas extras propias.