🔀 Ordenar colecciones en Java
🎯 Qué vamos a aprender
Hasta ahora hemos visto:
ListSetHashSetMapHashMap
Ahora toca ver cómo ordenar.
La idea más importante es esta:
Idea clave
Ordenar no significa lo mismo que guardar ordenado.
- Una colección puede estar desordenada y luego ordenarla.
- O puede ser una colección que ya mantiene orden automáticamente.
🧠 Lo primero: qué colecciones se ordenan y cuáles no
📋 List
Una List sí se puede ordenar directamente.
Ejemplo:
List<String> nombres = new ArrayList<>();
nombres.add("Luis");
nombres.add("Ana");
nombres.add("Pedro");
Esta lista se puede ordenar.
📦 HashSet
Un HashSet no guarda los elementos ordenados.
Set<String> nombres = new HashSet<>();
nombres.add("Luis");
nombres.add("Ana");
nombres.add("Pedro");
Aquí el orden de salida es impredecible.
Si quieres ordenarlo, normalmente lo pasas a lista y luego ordenas.
🗂️ HashMap
Un HashMap tampoco guarda las claves ordenadas.
Map<String, Integer> notas = new HashMap<>();
notas.put("Luis", 7);
notas.put("Ana", 9);
notas.put("Pedro", 6);
Si quieres ordenarlo, normalmente conviertes sus elementos a una lista:
List<Map.Entry<String, Integer>> entradas = new ArrayList<>(notas.entrySet());
Y luego ordenas esa lista.
✅ Formas de ordenar una lista
La forma más actual para ordenar una List es utilizar el método sort.
El método sort acepta dos formas de ordenación:
- Usando Comparable o,
- Usando Comparator
lista.sort(comparador); //usa Comparator
Y si los elementos ya tienen un orden natural (usa Comparable):
lista.sort(null);
🧭 Comparable
Sirve para definir el orden natural de una clase. El criterio de ordenación va dentro de la clase.
Comparable es una interfaz que sirve para decir:
“los objetos de esta clase saben ordenarse solos”
Por ejemplo:
- una cadena se ordena alfabéticamente
- un número se ordena de menor a mayor
- una clase
Alumnopodría ordenarse por nombre o por nota, si tú decides que ese será su orden “por defecto”
Si haces que tu clase Alumno implemente Comparable<Alumno>, entonces cada alumno “sabe” compararse con otro alumno.
Por ejemplo: vamos a decidir que el orden natural de un alumno sea por nombre.
public class Alumno implements Comparable<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;
}
@Override
public int compareTo(Alumno otro) {
return this.nombre.compareTo(otro.nombre);
}
@Override
public String toString() {
return nombre + " (" + nota + ")";
}
}
public class Main {
public static void main(String[] args) {
List<Alumno> alumnos = new ArrayList<>();
alumnos.add(new Alumno("Luis", 7));
alumnos.add(new Alumno("Ana", 9));
alumnos.add(new Alumno("Pedro", 5));
alumnos.sort(null); // usa el orden natural de la clase: Comparable
System.out.println(alumnos);
}
}
⚖️ Comparator
Comparator es una interfaz que sirve para decir:
“quiero ordenar estos objetos, pero el criterio lo decido desde fuera de la clase”
A diferencia de Comparable, donde el orden va dentro de la propia clase, con Comparator el orden va aparte.
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;
}
@Override
public String toString() {
return nombre + " (" + nota + ")";
}
}
Aquí Alumno no implementa Comparable. Por tanto, los alumnos no tienen orden natural propio. Si quieres ordenarlos, necesitas decirle a Java cómo compararlos. Ahí entra Comparator.
Puedes crear una clase que implemente la interfaz Comparator y ahí decirle cómo ordenarlo, por ejemplo:
Comparator por nombre
import java.util.Comparator;
class ComparadorPorNombre implements Comparator<Alumno> {
@Override
public int compare(Alumno a1, Alumno a2) {
return a1.getNombre().compareTo(a2.getNombre());
}
}
Ahora cuando llamemos al método sort, le pasamos un objeto de esta clase:
public class Main {
public static void main(String[] args) {
List<Alumno> alumnos = new ArrayList<>();
alumnos.add(new Alumno("Luis", 7));
alumnos.add(new Alumno("Ana", 9));
alumnos.add(new Alumno("Pedro", 5));
alumnos.sort(new ComparadorPorNombre());
System.out.println(alumnos);
}
}
⚖️ Diferencia entre Comparable y Comparator
| Elemento | Qué hace | Dónde se define | Cuándo usarlo |
|---|---|---|---|
Comparable |
Define el orden natural del objeto | Dentro de la propia clase | Cuando quieres un orden por defecto |
Comparator |
Define un criterio de orden externo | En otra clase o en una clase anónima | Cuando quieres varios órdenes distintos |
🚦 Regla fácil
✅ Usa Comparable cuando...
- el objeto tiene un orden natural claro
- quieres que se pueda ordenar “solo”
✅ Usa Comparator cuando...
- quieres varios criterios de ordenación
- no quieres modificar la clase
- quieres ordenar por cosas diferentes según el momento
👶 Explicado fácil
Imagina una clase Alumno.
Un alumno se puede ordenar por:
- nombre
- nota
- edad
Si usas Comparable
Estás diciendo:
“Para esta clase, el orden normal será ESTE”.
Solo hay un orden natural.
Si usas Comparator
Estás diciendo:
“Hoy quiero ordenar por nombre, mañana por nota, pasado por edad”.
Eso da mucha más flexibilidad.
🧱 Ejemplo 1: ordenar una lista de String
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> nombres = new ArrayList<>();
nombres.add("Luis");
nombres.add("Ana");
nombres.add("Pedro");
nombres.add("Bea");
nombres.sort(null); // orden natural, String ya implementa Comparable
System.out.println(nombres);
}
}
📝 Resultado
[Ana, Bea, Luis, Pedro]
🔤 Ejemplo 2: ordenar al revés con Comparator
Sin lambdas todavía, podemos usar una clase anónima.
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> nombres = new ArrayList<>();
nombres.add("Luis");
nombres.add("Ana");
nombres.add("Pedro");
nombres.add("Bea");
nombres.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
System.out.println(nombres);
}
}
🧠 Qué hace compare
El método compare(o1, o2) devuelve:
- negativo →
o1va antes - 0 → se consideran iguales para ese criterio
- positivo →
o1va después
🏫 Ejemplo 3: ordenar objetos con Comparator
Clase Alumno
public class Alumno {
private String nombre;
private int nota;
private int edad;
public Alumno(String nombre, int nota, int edad) {
this.nombre = nombre;
this.nota = nota;
this.edad = edad;
}
public String getNombre() {
return nombre;
}
public int getNota() {
return nota;
}
public int getEdad() {
return edad;
}
@Override
public String toString() {
return nombre + " - nota: " + nota + " - edad: " + edad;
}
}
Ordenar por nombre
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Alumno> alumnos = new ArrayList<>();
alumnos.add(new Alumno("Luis", 7, 20));
alumnos.add(new Alumno("Ana", 9, 19));
alumnos.add(new Alumno("Pedro", 6, 21));
alumnos.sort(new Comparator<Alumno>() {
@Override
public int compare(Alumno a1, Alumno a2) {
return a1.getNombre().compareTo(a2.getNombre());
}
});
System.out.println(alumnos);
}
}
📊 Ejemplo 4: ordenar por nota de mayor a menor
alumnos.sort(new Comparator<Alumno>() {
@Override
public int compare(Alumno a1, Alumno a2) {
return Integer.compare(a2.getNota(), a1.getNota());
}
});
🧠 Ojo
Para comparar enteros, mejor esto:
Integer.compare(a1.getNota(), a2.getNota())
que hacer esto:
a1.getNota() - a2.getNota()
porque Integer.compare(...) es más claro y evita problemas en comparaciones numéricas.
🧩 Ejemplo 5: si empatan en nota, ordenar por nombre
Ejemplo con clase anónima:
alumnos.sort(new Comparator<Alumno>() {
@Override
public int compare(Alumno a1, Alumno a2) {
int resultado = Integer.compare(a2.getNota(), a1.getNota());
if (resultado != 0) {
return resultado;
}
return a1.getNombre().compareTo(a2.getNombre());
}
});
🏷️ Crear comparadores en clases separadas
Esto suele ser muy útil para enseñar bien.
Comparator por nombre
import java.util.Comparator;
public class ComparadorAlumnoPorNombre implements Comparator<Alumno> {
@Override
public int compare(Alumno a1, Alumno a2) {
return a1.getNombre().compareTo(a2.getNombre());
}
}
Comparator por nota descendente
import java.util.Comparator;
public class ComparadorAlumnoPorNotaDesc implements Comparator<Alumno> {
@Override
public int compare(Alumno a1, Alumno a2) {
return Integer.compare(a2.getNota(), a1.getNota());
}
}
Uso
alumnos.sort(new ComparadorAlumnoPorNombre());
alumnos.sort(new ComparadorAlumnoPorNotaDesc());
Para clase
Como todavía no habéis visto lambdas, esto es probablemente lo más claro para empezar.
🚫 HashSet y HashMap NO ordenan
Esto es importantísimo:
| Colección | ¿Mantiene orden? |
|---|---|
ArrayList |
No, pero se puede ordenar |
LinkedList |
No, pero se puede ordenar |
HashSet |
No |
HashMap |
No |
🔄 Cómo ordenar un HashSet
Set<String> nombres = new HashSet<>();
nombres.add("Luis");
nombres.add("Ana");
nombres.add("Pedro");
List<String> lista = new ArrayList<>(nombres);
lista.sort(null);
System.out.println(lista);
🧾 Cómo ordenar un HashMap por clave
Map<String, Integer> notas = new HashMap<>();
notas.put("Luis", 7);
notas.put("Ana", 9);
notas.put("Pedro", 6);
List<Map.Entry<String, Integer>> entradas = new ArrayList<>(notas.entrySet());
entradas.sort(new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> e1, Map.Entry<String, Integer> e2) {
return e1.getKey().compareTo(e2.getKey());
}
});
//o también se puede hacer:
entradas.sort(Map.Entry.comparingByKey());
//o si quieres invertir el orden:
entradas.sort(Map.Entry.<String, Integer>comparingByKey().reversed());
🧾 Cómo ordenar un HashMap por valor
Map<String, Integer> notas = new HashMap<>();
notas.put("Luis", 7);
notas.put("Ana", 9);
notas.put("Pedro", 6);
List<Map.Entry<String, Integer>> entradas = new ArrayList<>(notas.entrySet());
entradas.sort(new Comparator<Map.Entry<String, Integer>>() {
@Override
public int compare(Map.Entry<String, Integer> e1, Map.Entry<String, Integer> e2) {
return Integer.compare(e1.getValue(), e2.getValue());
}
});
//o también se puede hacer:
entradas.sort(Map.Entry.comparingByValue());
//o si quieres invertir el orden:
entradas.sort(Map.Entry.<String, Integer>comparingByValue().reversed());
🧨 Error típico: creer que ordenar cambia un HashSet o un HashMap
No.
Lo que haces normalmente es:
- crear una lista auxiliar
- ordenar esa lista
- mostrarla o trabajar con ella
Pero el HashSet o el HashMap original siguen siendo hash, no “mágicamente ordenados”.
🧰 Métodos útiles de Comparator
Comparator no sirve solo para crear comparadores personalizados. También tiene varios métodos muy útiles que conviene conocer.
✅ Comparator.naturalOrder()
Devuelve un comparador que ordena usando el orden natural del objeto. Es decir:
- números: de menor a mayor
- cadenas: orden alfabético
- fechas: de más antigua a más reciente
List<String> nombres = new ArrayList<>();
nombres.add("Luis");
nombres.add("Ana");
nombres.add("Pedro");
nombres.sort(Comparator.naturalOrder());
System.out.println(nombres); // [Ana, Luis, Pedro]
📌 Es parecido a usar:
nombres.sort(null);
pero Comparator.naturalOrder() se ve más explícito y más claro para aprender.
🔁 Comparator.reverseOrder()
Devuelve un comparador con el orden natural inverso.
List<Integer> numeros = new ArrayList<>();
numeros.add(8);
numeros.add(2);
numeros.add(5);
numeros.sort(Comparator.reverseOrder());
System.out.println(numeros); // [8, 5, 2]
↩️ reversed()
Invierte un comparador que ya tienes creado.
Comparator<String> comp = Comparator.naturalOrder();
Comparator<String> compInverso = comp.reversed();
List<String> nombres = new ArrayList<>();
nombres.add("Luis");
nombres.add("Ana");
nombres.add("Pedro");
nombres.sort(compInverso);
System.out.println(nombres); // [Pedro, Luis, Ana]
📌 Diferencia importante:
Comparator.reverseOrder()crea el comparador inverso del orden naturalreversed()invierte el comparador actual
🧮 comparing(...), comparingInt(...), comparingDouble(...), comparingLong(...)
comparing(...) crea un Comparator a partir de un criterio. Le dices por qué atributo quieres ordenar.
Son métodos muy usados hoy para crear comparadores a partir de un atributo.
Ejemplos:
//ordena por nombre
Comparator<Alumno> porNombre = Comparator.comparing(Alumno::getNombre);
//ordena por nota
Comparator<Alumno> porNota = Comparator.comparingInt(Alumno::getNota);
Comparator.comparing(...) acepta una función que extrae una clave de ordenación y devuelve un Comparator<T> que compara usando esa clave.
Es el equivalente a este código:
Comparator<Alumno> c = Comparator.comparing(Alumno::getNombre);
//código equivalente
class ComparadorPorNombre implements Comparator<Alumno> {
@Override
public int compare(Alumno a1, Alumno a2) {
return a1.getNombre().compareTo(a2.getNombre());
}
}
Evitas escribir toda esa lógica repetida.
🥇 thenComparing(...)
Sirve para hacer una segunda comparación si la primera da empate.
Ejemplo: ordenar primero por nota y, si empatan, por nombre.
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;
}
@Override
public String toString() {
return nombre + " (" + nota + ")";
}
}
Comparator<Alumno> comp = Comparator
.comparingInt(Alumno::getNota)
.thenComparing(Alumno::getNombre);
⚠️ Este ejemplo usa referencias a método, que puedes dejar para más adelante si todavía no has visto lambdas. La idea importante es esta: thenComparing(...) permite desempatar. Comparación lexicográfica: si el primer criterio empata, usa el segundo.
⚪ nullsFirst(...) y nullsLast(...)
Sirven para ordenar cuando puede haber valores null.
nullsFirst(...)pone losnullal principionullsLast(...)pone losnullal final
Comparator<String> comp = Comparator.nullsLast(Comparator.naturalOrder());
//significa: los null van primero, lo demás se ordena normalmente
✅ Ideas clave
Listse puede ordenar consort(...)HashSetyHashMapno mantienen ordenComparable= orden natural del objetoComparator= criterio externo de ordenación- Si no hay lambdas, se puede usar
Comparatorcon clases separadas o clases anónimas
🧪 Mini ejemplo comparativo final
List<String> nombres = new ArrayList<>();
nombres.add("Luis");
nombres.add("Ana");
nombres.add("Pedro");
// Orden natural
nombres.sort(null);
// Orden inverso
nombres.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});