Ordenación y Comparación

Acabamos de ver que con TreeSets se insertan los objetos de manera ordenada ascendentemente, pero cuando usamos objetos más complejos que un Integer necesitamos establecer como queremos que los objetos se ordenen, una condiciones de ordenación que debemos establecer. Hay dos formas:

  • Que las clases usen interfaz Comparable.
  • O crear una clase Comparator.

Nota: Usaremos TreeSet para estos ejemplos pero es aplicable para casi todas las colecciones.

Implementando la interfaz Comparable

Dicha interfaz sólo define un método:

public interface Comparable<T> {
 int compareTo(T o);
}

Este método devolverá:

  • Negativo: this menor que “o” (parámetro de entrada del método).
  • Cero: this igual que “o”.
  • Positivo: this mayor que “o”.

Ejemplo:

import java.time.LocalDateTime;
public class User implements Comparable<User> {
    private int id; // id del usuario
    private String loginName; // login de inicio de sesión
    private LocalDateTime lastLogin; // último inicio de sesión
 
    User(int id, String loginName) {
        this.id = id;
        this.loginName = loginName;
    }
 
    public LocalDateTime getlLastLogin() { return this.lastLogin; }
    public void regSystemLogin() { this.lastLogin = LocalDateTime.now(); }
 
    @Override
    public String toString() {
        return "{" + this.id + ": " + this.loginName + ": " + this.lastLogin + "}";
    }

    @Override
    public int compareTo(User other) {
        // Comparamos los id de ambos usuarios. Podemos aprovechamos del propio método compareTo()
        // de la clase Integer pues la clase Integer implementa el interfaz Comparable
        return Integer.valueOf(this.id).compareTo(other.id);
    }
 
    @Override
    public boolean equals(User other) {
        return this.id == other.id;
    }

    @Override
    public int hashCode() {
        int result = Integer.hashCode(this.id);
        result = 31*result + this.loginName.hashCode();
        result = 31*result + ((this.lastLogin==null)? 0: this.lastLogin.hashCode());
        return result;
    }

Como observais en el anterior ejemplo también están implementados el hashCode() y el equals().

Creando un Comparator

De esta manera es necesario crear una clase a parte que implemente la interfaz Comparator. Nos ofrece más flexibilidad y posibilidades que la anterior forma. Y podemos usar esta clase para otras clases que compartan mismos criterios de ordenación. En lo que coincide con la anterior forma es que debemos implementar el método compare(), pero recibirá los dos objetos a comparar. En este ejemplo se ordenará por el último acceso a los usuarios:

import java.time.LocalDateTime;
class UserComparator implements Comparator<User> {
 @Override
 public int compare(User u1, user u2) {
    // Podemos aprovechamos del propio método compareTo() de la clase LocalDateTime
    // ya que implementa el interfaz Comparable
    return u1.getLastLogin().compareTo(u2.getLastLogin());
 }
}

Una vez creada la clase podemos “insertarla” en el constructor del TreeSet:

 TreeSet<User> users2 = new TreeSet<>(new UserComparator()); 

Pero también al método sort() de Collections y Arrays.

Uso de Collections.sort()

Por último también podemos utilizar un método de Collection que modifica la coleción, es el sort():

List<String> fruits = new ArrayList<String>();
fruits.add("Apple");
fruits.add("Orange");
fruits.add("Banana");
fruits.add("Grape");

Collections.sort(fruits);
System.out.println(fruits);

Con objetos “de verdad” seguimos utilizando el compareTo():

public class Fruit implements Comparable<Object>{
    private int id;
    private String name;
    private String taste;

    Fruit(int id, String name, String taste){
        this.id=id;
        this.name=name;
        this.taste=taste;
    }
    @Override 
    public int compareTo(Object o) {
        Fruit f = (Fruit) o; 
        return this.id - f.id ;
    }
}

//Uso el mismo
Collections.sort(fruitList);
fruitList.forEach(fruit -> {
    System.out.println(fruit.getId() + " " + fruit.getName() + " " + 
      fruit.getTaste());
});

El .sort() también acepta una clase Comparator:

class SortByName implements Comparator<Fruit> {
    @Override
    public int compare(Fruit a, Fruit b) {
        return a.getName().compareTo(b.getName());
    }
}
//Uso
Collections.sort(fruitList, new SortByName());

Método estático Compare

Los clases wrappers de java tienen un método estático para comparar, y se comporta de la misma manera que lo anteriormente visto. Ejemplo de uso con Integer:

int val1 = 200;
int val2 = 250;
int val3 = 200;
System.out.println(Integer.compare(val1, val2));
System.out.println(Integer.compare(val1, val3));

Ejercicio 1

Partiendo de la clase Articulo, crea una lista de objetos que los almacene y los ordene por el código del artículo:

class Articulo {
 String codArticulo;
 String descripcion;
 int cantidad;
 Articulo(String codArticulo, String descripcion, int cantidad) {
    this.codArticulo = codArticulo;
    this.descripcion = descripcion;
    this.cantidad = cantidad;
 }

Implememta todo lo necesario.

Luego implementa la ordenación de menos a más cantidad de los articulos.

Ejercicio 2

Añade al siguiente código el comparador necesario para que los teléfonos aparezcan ordenados (Strings). Primero aparecerán ordenados de mayor a menor los números locales (sin símbolo “+” delante) y después los internacionales, también ordenados de mayor a menor: Input: 981555555 34981565656 666666666 +34666666666

Resultado: 981555555 666666666 +34981565656 +34666666666

Podéis probar las dos formas que hemos visto de implementar la ordenación.

Ejercicio 3

Partiendo de una clase estudiante con nombre y edad. Nos piden agregarlo a una lista y que los objetos se ordenen primero por su nombre y luego por la edad (dos condiciones de ordenación). Usa Collections.sort().