- Ordenamiento Básico de Colecciones y Arreglos
El enfoque más directo para ordenar elementos en Java implica utilizar las utilidades integradas en las clases Arrays y Collections. Estas herramientas permiten manipular el orden de los datos de manera ascendente o descendente con mínima configuración.
List<Integer> cifras = new ArrayList<>(Arrays.asList(42, 15, 8, 99, 23));
// Ordenamiento ascendente por defecto
Collections.sort(cifras);
// Ordenamiento descendente utilizando el comparador inverso nativo
cifras.sort(Collections.reverseOrder());
// Ordenamiento descendente mediante expresiones lambda
cifras.sort((x, y) -> y.compareTo(x));
- Ordenamiento Intrínseco mediante la Interfaz Comparable
Cuando una clase requiere un ordenamiento natural predefinido, debe implementar la interfaz Comparable. Esto define el comportamiento por defecto cuando se invoca el método de ordenamiento sin pasar un comparador externo.
import java.util.*;
public class Producto implements Comparable<Producto> {
private String nombre;
private double precio;
private int existencias;
public Producto(String nombre, double precio, int existencias) {
this.nombre = nombre;
this.precio = precio;
this.existencias = existencias;
}
public String getNombre() { return nombre; }
public double getPrecio() { return precio; }
public int getExistencias() { return existencias; }
@Override
public int compareTo(Producto otro) {
// Criterio 1: Precio de mayor a menor (usando compare para evitar desbordamiento)
int comparacionPrecio = Double.compare(otro.precio, this.precio);
if (comparacionPrecio != 0) return comparacionPrecio;
// Criterio 2: Existencias de menor a mayor
int comparacionStock = Integer.compare(this.existencias, otro.existencias);
if (comparacionStock != 0) return comparacionStock;
// Criterio 3: Nombre en orden alfabético
return this.nombre.compareTo(otro.nombre);
}
@Override
public String toString() {
return String.format("%s ($%.2f, Stock: %d)", nombre, precio, existencias);
}
}
public class DemoComparable {
public static void main(String[] args) {
List<Producto> catalogo = Arrays.asList(
new Producto("Laptop", 1200.50, 15),
new Producto("Mouse", 25.00, 150),
new Producto("Teclado", 75.00, 45),
new Producto("Monitor", 300.00, 20),
new Producto("Webcam", 75.00, 30)
);
System.out.println("Catálogo original:");
catalogo.forEach(System.out::println);
// Se invoca directamente el ordenamiento natural
Collections.sort(catalogo);
System.out.println("\nCatálogo ordenado (Precio desc -> Stock asc -> Nombre asc):");
catalogo.forEach(System.out::println);
}
}
Es importante recordar las reglas de ordenamiento natural de las clases base del JDK: los tipos numéricos envoltorios y BigDecimal se ordenan por magnitud ascendente; las cadenas (String) evalúan los valores Unicode carácter por carácter y, en caso de empate, por longitud; las clases de fecha y hora se ordenan cronológicamente de la más antigua a la más reciente.
- Ordenamiento Extrínseco y Complejo con Comparator
La interfaz Comparator permite desacoplar la lógica de ordenamiento de la clase de dominio, ofreciendo una flexibilidad total para definir múltiples criterios sin modificar el código fuente oirginal de la entidad.
class Vehiculo {
String fabricante;
int anioFabricacion;
double kilometraje;
// Getters requeridos para las referencias de métodos
public String getFabricante() { return fabricante; }
public int getAnioFabricacion() { return anioFabricacion; }
public double getKilometraje() { return kilometraje; }
}
List<Vehiculo> inventario = new ArrayList<>();
Comparator<Vehiculo> logicaInventario = Comparator
.comparing(Vehiculo::getFabricante) // Fabricante ascendente
.thenComparing(Vehiculo::getAnioFabricacion, Comparator.reverseOrder()) // Año descendente
.thenComparingDouble(Vehiculo::getKilometraje); // Kilometraje ascendente
inventario.sort(logicaInventario);
Para escenarios de negocio que requieren reglas condicionales complejas, es posible instanciar un Comparator personalizado mediante expresiones lambda:
class CuentaBancaria {
String titular;
boolean esPremium;
BigDecimal saldo;
public boolean esPremium() { return esPremium; }
public BigDecimal getSaldo() { return saldo; }
}
Comparator<CuentaBancaria> prioridadPremium = (c1, c2) -> {
// Regla de negocio: Las cuentas Premium siempre van primero
if (c1.esPremium() && !c2.esPremium()) return -1;
if (!c1.esPremium() && c2.esPremium()) return 1;
// Si el estatus es idéntico, se ordena por saldo de mayor a menor
return c2.getSaldo().compareTo(c1.getSaldo());
};
- Paradigma Funcional con Java Streams
Desde la introducción de la API de Streams, el ordenamiento puede integrarse de manera fluida dentro de una tubería de procesamiento de datos funcoinal, manteniendo la inmutabilidad de la colección original.
// Ordenamiento simple por un único atributo
List<Cliente> clientesOrdenados = listaClientes.stream()
.sorted(Comparator.comparing(Cliente::getEdad))
.collect(Collectors.toList());
// Encadenamiento de múltiples criterios dentro del flujo
List<Cliente> clientesMultiCriterio = listaClientes.stream()
.sorted(Comparator.comparing(Cliente::getDepartamento)
.thenComparing(Cliente::getApellido))
.collect(Collectors.toList());
- Procesamiento Concurrente para Grandes Volúmenes
Cuando se procesan conjuntos de datos masivos (generalmente a partir de cientos de miles de elementos), el ordenamiento paralelo puede reducir significativamente el tiempo de ejecución al dividir la carga de trabajo entre múltiples hilos. Sin embargo, para colecciones pequeñas, la sobrecarga del framework Fork/Join puede degradar el rendimiento.
// Ordenamiento paralelo para arreglos primitivos
long[] metricasRendimiento = {99234L, 1287L, 55432L, 8912L};
Arrays.parallelSort(metricasRendimiento);
// Flujo paralelo para colecciones de objetos complejos
List<Cliente> clientesRapidos = listaClientes.parallelStream()
.sorted(Comparator.comparing(Cliente::getUltimaCompra))
.collect(Collectors.toList());