Saltar a contenido

🔀 Ordenar colecciones en Java

🎯 Qué vamos a aprender

Hasta ahora hemos visto:

  • List
  • Set
  • HashSet
  • Map
  • HashMap

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 Alumno podrí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:

  • negativoo1 va antes
  • 0 → se consideran iguales para ese criterio
  • positivoo1 va 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 natural
  • reversed() 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 los null al principio
  • nullsLast(...) pone los null al final
Comparator<String> comp = Comparator.nullsLast(Comparator.naturalOrder());
//significa: los null van primero, lo demás se ordena normalmente

✅ Ideas clave

  • List se puede ordenar con sort(...)
  • HashSet y HashMap no mantienen orden
  • Comparable = orden natural del objeto
  • Comparator = criterio externo de ordenación
  • Si no hay lambdas, se puede usar Comparator con 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);
    }
});