1. El problema del orden en las colecciones clásicas
En Java siempre ha habido colecciones que garantizan el orden de los elementos (por ejemplo, ArrayList, LinkedList, LinkedHashSet, LinkedHashMap) y otras en las que el orden no está garantizado (por ejemplo, HashSet, HashMap). Pero todas ellas tenían un inconveniente común: aunque algunas almacenan los elementos en un orden determinado, las interfaces estándar (List, Set, Map) no proporcionaban métodos universales para acceder al primer y al último elemento ni para invertir el orden.
Por ejemplo, si tienes List<String>, puedes obtener el primer elemento con list.get(0), pero en un Set o un Map ese truco ya no sirve — tendrás que usar iteradores o escribir código adicional. Convengamos en que no es muy cómodo ni favorece la legibilidad.
Es como si tuvieras un mueble con cajones y, para coger el primero o el último, tuvieras que volver a contar todos los cajones a mano cada vez. Sería genial tener unas asas especiales «primero» y «último», ¿verdad?
La aparición de SequencedCollection y de las interfaces relacionadas
En Java 21 aparecieron nuevas interfaces de colecciones:
- SequencedCollection<E>
- SequencedSet<E>
- SequencedMap<K, V>
Estas interfaces amplían las colecciones estándar e introducen un enfoque unificado para trabajar con el orden de los elementos. Ahora puedes escribir código universal para cualquier colección en la que el orden importe, sin preocuparte por la implementación concreta.
¿Qué son?
- SequencedCollection — es una colección cuyos elementos tienen un orden determinado y en la que puedes obtener fácilmente el primer y el último elemento, así como invertir el orden.
- SequencedSet — lo mismo, pero para conjuntos (elementos únicos).
- SequencedMap — lo mismo, pero para mapas (clave-valor).
¿Qué colecciones los implementan ahora?
En Java 21 las siguientes colecciones estándar implementan las nuevas interfaces:
- ArrayList, LinkedList → SequencedCollection
- LinkedHashSet, TreeSet → SequencedSet
- LinkedHashMap, TreeMap → SequencedMap
Esto significa que, si ya usas estas colecciones, obtienes automáticamente las nuevas capacidades.
2. Métodos principales de SequencedCollection, SequencedSet, SequencedMap
Métodos de SequencedCollection
E getFirst(); // Obtener el primer elemento
E getLast(); // Obtener el último elemento
SequencedCollection<E> reversed(); // Obtener la colección en orden inverso
Métodos de SequencedSet
Los mismos métodos que en SequencedCollection, además de todo lo que hace Set.
Métodos de SequencedMap
Map.Entry<K, V> firstEntry(); // Primer elemento (clave-valor)
Map.Entry<K, V> lastEntry(); // Último elemento (clave-valor)
SequencedMap<K, V> reversed(); // Mapa en orden inverso
3. Ejemplos de uso de las nuevas interfaces
Ejemplo 1: Obtener el primer y el último elemento
import java.util.*;
public class SequencedDemo {
public static void main(String[] args) {
SequencedCollection<String> sc = new ArrayList<>();
sc.add("Java");
sc.add("Python");
sc.add("Kotlin");
// Obtener el primer y el último elemento
String first = sc.getFirst(); // "Java"
String last = sc.getLast(); // "Kotlin"
System.out.println("Primero: " + first);
System.out.println("Último: " + last);
}
}
Salida:
Primero: Java
Último: Kotlin
Ejemplo 2: Inversión de la colección
SequencedCollection<String> sc = new LinkedList<>();
sc.add("A");
sc.add("B");
sc.add("C");
SequencedCollection<String> reversed = sc.reversed();
System.out.println(reversed); // [C, B, A]
Ten en cuenta: reversed() devuelve una vista de la colección en orden inverso. Si modificas la colección original, también cambiará la vista reversed.
Ejemplo 3: Trabajo con SequencedSet
SequencedSet<Integer> set = new LinkedHashSet<>();
set.add(100);
set.add(200);
set.add(300);
System.out.println("Primer elemento: " + set.getFirst()); // 100
System.out.println("Último elemento: " + set.getLast()); // 300
SequencedSet<Integer> reversedSet = set.reversed();
System.out.println(reversedSet); // [300, 200, 100]
Ejemplo 4: Trabajo con SequencedMap
SequencedMap<String, Integer> map = new LinkedHashMap<>();
map.put("apple", 5);
map.put("banana", 3);
map.put("cherry", 7);
Map.Entry<String, Integer> first = map.firstEntry();
Map.Entry<String, Integer> last = map.lastEntry();
System.out.println("Primero: " + first.getKey() + " = " + first.getValue()); // apple = 5
System.out.println("Último: " + last.getKey() + " = " + last.getValue()); // cherry = 7
SequencedMap<String, Integer> reversedMap = map.reversed();
System.out.println(reversedMap); // {cherry=7, banana=3, apple=5}
4. ¿Cómo se relaciona esto con tu aplicación?
Supongamos que, en tu aplicación educativa, mantienes una lista de usuarios que han iniciado sesión y quieres obtener rápidamente el primero y el último (por ejemplo, para mostrar «quién entró primero» y «quién lo hizo último»). Antes había que escribir algo como:
List<String> users = new ArrayList<>();
// ... añadimos usuarios
String first = users.get(0);
String last = users.get(users.size() - 1);
Pero si la colección no es una lista sino, por ejemplo, LinkedHashSet (donde los elementos son únicos y se mantiene el orden), ese truco ya no funciona:
Set<String> users = new LinkedHashSet<>();
// ... añadimos usuarios
// ¿Cómo obtener el primero? Solo mediante un iterador:
String first = users.iterator().next();
// ¿Y el último? ¡Solo recorriendo todos los elementos!
Ahora todo es más sencillo y universal:
SequencedSet<String> users = new LinkedHashSet<>();
// ... añadimos usuarios
String first = users.getFirst();
String last = users.getLast();
Esto no solo reduce la cantidad de código, sino que también lo hace más legible y seguro.
5. Esquema de las nuevas interfaces
classDiagram
Collection <|-- SequencedCollection
List <|-- SequencedCollection
Set <|-- SequencedSet
Map <|-- SequencedMap
SequencedCollection <|-- SequencedSet
SequencedSet <|-- LinkedHashSet
SequencedSet <|-- TreeSet
SequencedCollection <|-- ArrayList
SequencedCollection <|-- LinkedList
SequencedMap <|-- LinkedHashMap
SequencedMap <|-- TreeMap
6. Matices útiles
Ventajas prácticas de SequencedCollection
- Interfaz unificada para trabajar con el orden: Ya no hace falta recordar dónde existe get(0), dónde necesitas un iterador y dónde ni siquiera puedes obtener el primer elemento.
- Comodidad al trabajar con colas y pilas: Es fácil obtener el primer (head) y el último (tail) elemento.
- Seguridad y legibilidad: Menos errores relacionados con el uso incorrecto de colecciones; el código se vuelve autoexplicativo.
- Inversión rápida de la colección: El método reversed() permite obtener fácilmente el orden inverso sin manipulaciones manuales.
- Facilidad de mantenimiento y ampliación del código: Si en el futuro quieres sustituir, por ejemplo, ArrayList por LinkedHashSet, el código que usa SequencedCollection no habrá que reescribirlo.
Particularidades de la implementación
- No todas las colecciones implementan SequencedCollection: Por ejemplo, HashSet y HashMap no garantizan el orden, por lo que no implementan las nuevas interfaces.
- Los métodos pueden lanzar excepciones: Si la colección está vacía, llamar a getFirst() o getLast() provocará una NoSuchElementException. No olvides comprobar que la colección no esté vacía.
- reversed() — es una vista, no una copia: Los cambios en la colección original se reflejan en la vista invertida y viceversa.
- Compatibilidad: Las nuevas interfaces están disponibles solo a partir de Java 21. Si usas una versión de Java anterior, estas capacidades aún no están disponibles (¡pero es una excelente razón para actualizar!).
- Genéricos: Todas las nuevas interfaces admiten totalmente genéricos, así que puedes trabajar con cualquier tipo de datos.
7. Errores típicos al trabajar con SequencedCollection
Error n.º 1: esperar soporte de orden en colecciones que no lo garantizan. Si intentas convertir HashSet a SequencedSet, obtendrás un error de compilación — HashSet no tiene orden y no implementa esa interfaz.
Error n.º 2: ignorar colecciones vacías. Llamar a getFirst() o getLast() en una colección vacía lanzará una excepción. Antes de llamar a estos métodos, comprueba que la colección no esté vacía:
if (!sc.isEmpty()) {
String first = sc.getFirst();
}
Error n.º 3: malentendidos con reversed(). El método reversed() devuelve una vista, no una copia. Si modificas la vista reversed, cambiará también la colección original (y viceversa). Esto puede conducir a resultados inesperados si no cuentas con ese comportamiento.
Error n.º 4: usar las nuevas interfaces en versiones antiguas de Java. Si tu proyecto se compila con una versión inferior a Java 21, el compilador no encontrará estas interfaces. ¡Comprueba la versión del JDK en la configuración del proyecto!
GO TO FULL VERSION