¡Hola! Hoy vamos a hablar de comparar objetos. Hmm... ¿Pero no hemos hablado ya de este tema más de una vez? :/ Sabemos cómo
==
funciona el operador, así como los métodos equals()
y hashCode()
. La comparación es un poco diferente. Anteriormente, lo más probable es que quisiéramos decir "comprobar la igualdad de los objetos". ¡Pero las razones para comparar objetos entre sí pueden ser completamente diferentes! El más obvio de ellos es la clasificación. Creo que si te dijeran que ordenaras ArrayList<>
números o cadenas, podrías manejar esto sin ningún problema:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
String name1 = "Masha";
String name2 = "Sasha";
String name3 = "Dasha";
List<String> names = new ArrayList<>();
names.add(name1);
names.add(name2);
names.add(name3);
Collections.sort(names);
System.out.println(names);
}
}
Salida de la consola:
[Dasha, Masha, Sasha]
Si recordaste la Collections
clase y su sort()
método, ¡bien hecho! Creo que tampoco tendrás problemas con los números. Aquí hay una tarea más desafiante para ti:
public class Car {
private int manufactureYear;
private String model;
private int maxSpeed;
public Car(int manufactureYear, String model, int maxSpeed) {
this.manufactureYear = manufactureYear;
this.model = model;
this.maxSpeed = maxSpeed;
}
// ...getters, setters, toString()
}
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Car> cars = new ArrayList<>();
Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
Car bugatti = new Car(2010, "Bugatti Veyron", 350);
cars.add(ferrari);
cars.add(bugatti);
cars.add(lambo);
}
}
La tarea es realmente simple. Tenemos una Car
clase y 3 objetos Car. ¿Sería tan amable de ordenar los autos en la lista? Probablemente te preguntarás: "¿Cómo deben ordenarse?" ¿Por nombre? Por año de fabricación? ¿Por la velocidad máxima? Excelente pregunta. Por el momento, no sabemos cómo ordenar los Car
objetos. Y, naturalmente, ¡Java tampoco lo sabe! Cuando intentamos pasar una lista de Car
objetos al Collections.sort()
método, obtenemos un error:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Car> cars = new ArrayList<>();
Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
Car lambo = new Car(20012, "Lamborghini Gallardo", 290);
Car bugatti = new Car(2010, "Bugatti Veyron", 350);
cars.add(ferrari);
cars.add(bugatti);
cars.add(lambo);
// Compilation error!
Collections.sort(cars);
}
}
Y, de hecho, ¿cómo sabría el lenguaje cómo ordenar los objetos de las clases que ha escrito? Esto depende de lo que su programa necesita hacer. De alguna manera debemos enseñar a Java a comparar estos objetos. Y compararlos como queramos. Java tiene un mecanismo especial para esto: la Comparable
interfaz. Para comparar y ordenar de alguna manera nuestros Car
objetos, la clase debe implementar esta interfaz, que consiste en un solo método compareTo()
:
public class Car implements Comparable<Car> {
private int manufactureYear;
private String model;
private int maxSpeed;
public Car(int manufactureYear, String model, int maxSpeed) {
this.manufactureYear = manufactureYear;
this.model = model;
this.maxSpeed = maxSpeed;
}
@Override
public int compareTo(Car o) {
return 0;
}
// ...getters, setters, toString()
}
tenga en cuentaque especificamos la Comparable<Car>
interfaz, no solo Comparable
. Esta es una interfaz parametrizada, es decir, debemos especificar la clase específica asociada. En principio, puede eliminarlos <Car>
de la interfaz, pero luego la comparación se basará en Object
los objetos de forma predeterminada. En lugar del compareTo(Car o)
método, nuestra clase tendrá:
@Override
public int compareTo(Object o) {
return 0;
}
Por supuesto, es mucho más fácil para nosotros trabajar con Car
. Dentro del compareTo()
método, implementamos nuestra lógica para comparar autos. Supongamos que necesitamos ordenarlos por año de fabricación. Probablemente notó que el compareTo()
método devuelve un int
, no un boolean
. No dejes que esto te sorprenda. Cuando comparamos dos objetos, hay 3 posibilidades:
а < b
a > b
a == b
.
boolean
tiene solo 2 valores: verdadero y falso, lo que no funciona bien para comparar objetos. Con int
, todo es mucho más sencillo. Si el valor devuelto es > 0
, entonces a > b
. Si el resultado de compareTo
es < 0
, entonces a < b
. Y, si el resultado es == 0
, entonces dos objetos son iguales: a == b
. Enseñar a nuestra clase a ordenar los autos por año de fabricación es fácil:
@Override
public int compareTo(Car o) {
return this.getManufactureYear() - o.getManufactureYear();
}
Pero, ¿qué está pasando aquí? Tomamos un objeto Car ( this
), obtenemos el año de fabricación de este auto y le restamos el año de fabricación de otro auto (con el que se compara el objeto). Si el año de fabricación del primer automóvil es mayor, el método devolverá un int > 0
. Esto significa que el this car >
coche o
. Por el contrario, si el año de fabricación del segundo automóvil ( о
) es mayor, el método arrojará un número negativo, lo que significa que o > this
. Finalmente, si son iguales, el método devolverá 0
. ¡Este simple mecanismo ya es suficiente para que ordenemos colecciones de Car
objetos! No tienes que hacer nada más. Échale un vistazo:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Car> cars = new ArrayList<>();
Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
Car bugatti = new Car(2010, "Bugatti Veyron", 350);
cars.add(ferrari);
cars.add(bugatti);
cars.add(lambo);
// There was previously an error here
Collections.sort(cars);
System.out.println(cars);
}
}
Salida de la consola:
[Car{manufactureYear=1990, model='Ferrari 360 Spider', maxSpeed=310},
Car{manufactureYear=2010, model='Bugatti Veyron', maxSpeed=350},
Car{manufactureYear=2012, model='Lamborghini Gallardo', maxSpeed=290}]
¡Los autos están ordenados como queremos! :) ¿ Cuándo debo usar Comparable
? El método de comparación implementado en Comparable
se llama ordenamiento natural. Esto se debe a que en el compareTo()
método define la forma más común o natural de comparar objetos de esta clase. Java ya tiene un ordenamiento natural. Por ejemplo, Java sabe que las cadenas se ordenan con mayor frecuencia alfabéticamente y los números aumentando el valor numérico. Por lo tanto, si llama al sort()
método en una lista de números o cadenas, se ordenarán. Si nuestro programa normalmente comparará y ordenará autos por año de fabricación, entonces debemos definir la clasificación natural para Autos usando la Comparable<Car>
interfaz y elcompareTo()
método. Pero, ¿y si esto no es suficiente para nosotros? Imaginemos que nuestro programa no es tan simple. En la mayoría de los casos, la clasificación natural de los automóviles (que hemos configurado para que se realice por año de fabricación) nos conviene. Pero a veces nuestros clientes son aficionados a la conducción rápida. Si estamos preparando un catálogo de autos para que lo examinen, los autos deben estar ordenados por velocidad máxima. Por ejemplo, supongamos que necesitamos ordenar así el 15% del tiempo. Claramente, esto no es suficiente para que establezcamos que la Car
clasificación natural de la clase sea por velocidad en lugar de por año de fabricación. Pero no podemos ignorar al 15% de nuestros clientes. ¿Asi que que hacemos? Otra interfaz viene en nuestra ayuda aquí: Comparator
. Al igual que Comparable
, es una interfaz parametrizada. ¿Cual es la diferencia? Comparable
hace que nuestros objetos sean "comparables" y define su orden de clasificación más natural, es decir, el orden de clasificación que se utilizará en la mayoría de los casos. Comparator
es una interfaz separada de "comparación". Si necesitamos implementar algún tipo de orden de clasificación especial, no necesitamos ingresar a la Car
clase y cambiar la lógica de compareTo()
. En cambio, podemos crear una clase separada que implemente Comparator y enseñarle cómo realizar la clasificación que necesitamos.
import java.util.Comparator;
public class MaxSpeedCarComparator implements Comparator<Car> {
@Override
public int compare(Car o1, Car o2) {
return o1.getMaxSpeed() - o2.getMaxSpeed();
}
}
Como puede ver, el nuestro Comparator
es bastante simple. Necesitamos implementar solo un método de interfaz: compare()
. Toma dos Car
objetos como entradas y compara sus velocidades máximas de la forma habitual (por sustracción). Como compareTo()
, devuelve an int
, y el principio de comparación es el mismo. ¿Cómo usamos esto? Todo es sencillo:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Car> cars = new ArrayList<>();
Car ferrari = new Car(1990, "Ferrari 360 Spider", 310);
Car lambo = new Car(2012, "Lamborghini Gallardo", 290);
Car bugatti = new Car(2010, "Bugatti Veyron", 350);
cars.add(ferrari);
cars.add(bugatti);
cars.add(lambo);
Comparator speedComparator = new MaxSpeedCarComparator();
Collections.sort(cars, speedComparator);
System.out.println(cars);
}
}
Salida de la consola:
[Car{manufactureYear=2012, model='Lamborghini Gallardo', maxSpeed=290},
Car{manufactureYear=1990, model='Ferrari 360 Spider', maxSpeed=310},
Car{manufactureYear=2010, model='Bugatti Veyron', maxSpeed=350}]
Simplemente creamos un objeto comparador y lo pasamos al Collections.sort()
método junto con la lista a ordenar. Cuando el sort()
método recibe un comparador, no utiliza la clasificación natural definida en el método Car
de la clase compareTo()
. En su lugar, aplica el algoritmo de clasificación definido por el comparador que se le pasa. ¿Cuáles son las ventajas de hacer esto? Primero, la compatibilidad con el código existente. Creamos un nuevo método de clasificación especial, mientras conservamos el existente que se usará la mayor parte del tiempo. No tocamos la Car
clase en absoluto. Era un Comparable
, y así sigue siendo:
public class Car implements Comparable<Car> {
private int manufactureYear;
private String model;
private int maxSpeed;
public Car(int manufactureYear, String model, int maxSpeed) {
this.manufactureYear = manufactureYear;
this.model = model;
this.maxSpeed = maxSpeed;
}
@Override
public int compareTo(Car o) {
return this.getManufactureYear() - o.getManufactureYear();
}
// ...getters, setters, toString()
}
Segundo, flexibilidad. Podemos agregar tantos algoritmos de clasificación como queramos. Por ejemplo, podemos ordenar los autos por color, velocidad, peso o por cuántas veces se ha usado un auto en las películas de Batman. Todo lo que tenemos que hacer es crear un archivo adicional Comparator
. ¡Eso es todo! Hoy has estudiado dos mecanismos muy importantes que utilizarás a menudo en proyectos reales en el trabajo. Pero, como sabes, la teoría sin la práctica no es nada. ¡Ahora es el momento de consolidar tus conocimientos y completar algunas tareas!
GO TO FULL VERSION