Oi! Hoje vamos falar sobre a comparação de objetos. Hmm... Mas já não falamos desse assunto mais de uma vez? :/ Sabemos como
==
funciona o operador, bem como os métodos equals()
e hashCode()
. A comparação é um pouco diferente. Anteriormente, provavelmente queríamos dizer "verificar objetos quanto à igualdade". Mas os motivos para comparar objetos entre si podem ser completamente diferentes! O mais óbvio deles é a classificação. Acho que se você fosse instruído a classificar um ArrayList<>
dos números ou strings, seria capaz de lidar com isso sem problemas:
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);
}
}
Saída do console:
[Dasha, Masha, Sasha]
Se você se lembrou da Collections
aula e seu sort()
método, muito bem! Acho que você também não terá problemas com números. Aqui está uma tarefa mais desafiadora para você:
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);
}
}
A tarefa é realmente simples. Temos uma Car
classe e 3 objetos Car. Você poderia gentilmente classificar os carros na lista? Você provavelmente perguntará: "Como eles devem ser classificados?" Por nome? Por ano de fabricação? Pela velocidade máxima? Excelente pergunta. No momento, não sabemos como classificar os Car
objetos. E, naturalmente, Java também não sabe disso! Quando tentamos passar uma lista de Car
objetos para o Collections.sort()
método, obtemos um erro:
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);
}
}
E, de fato, como a linguagem saberia classificar os objetos das classes que você escreveu? Isso depende do que seu programa precisa fazer. Devemos de alguma forma ensinar Java a comparar esses objetos. E compará-los como queremos. Java tem um mecanismo especial para isso: a Comparable
interface. Para comparar e classificar nossos Car
objetos de alguma forma, a classe deve implementar esta interface, que consiste em um único 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()
}
Observeque especificamos a Comparable<Car>
interface, não apenas Comparable
. Esta é uma interface parametrizada, ou seja, devemos especificar a classe específica associada. Em princípio, você pode remover <Car>
da interface, mas a comparação será baseada em Object
objetos por padrão. Ao invés do compareTo(Car o)
método, nossa classe terá:
@Override
public int compareTo(Object o) {
return 0;
}
Claro, é muito mais fácil para nós trabalharmos com Car
. Dentro do compareTo()
método, implementamos nossa lógica de comparação de carros. Suponha que precisamos classificá-los por ano de fabricação. Você provavelmente notou que o compareTo()
método retorna um int
, não um boolean
. Não deixe que isso o surpreenda. Quando comparamos dois objetos, existem 3 possibilidades:
а < b
a > b
a == b
.
boolean
tem apenas 2 valores: true e false, o que não funciona bem para comparar objetos. Com int
, tudo é muito mais simples. Se o valor de retorno for > 0
, então a > b
. Se o resultado de compareTo
for < 0
, então a < b
. E, se o resultado for == 0
, então dois objetos são iguais: a == b
. Ensinar nossa classe a classificar carros por ano de fabricação é fácil:
@Override
public int compareTo(Car o) {
return this.getManufactureYear() - o.getManufactureYear();
}
Mas o que está acontecendo aqui? Pegamos um objeto Carro ( this
), obtemos o ano de fabricação desse carro e subtraímos dele o ano de fabricação de outro carro (aquele com o qual o objeto está sendo comparado). Se o ano de fabricação do primeiro carro for maior, o método retornará um int > 0
. Isso significa que o this car >
carro o
. Por outro lado, se o ano de fabricação do segundo carro ( о
) for maior, o método retornará um número negativo, o que significa que o > this
. Por fim, se forem iguais, o método retornará 0
. Esse simples mecanismo já é suficiente para ordenarmos coleções de Car
objetos! Você não precisa fazer mais nada. Confira:
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);
}
}
Saída do console:
[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}]
Os carros são classificados como queremos! :) Quando devo usar Comparable
? O método de comparação implementado em Comparable
é chamado de ordenação natural. Isso porque no compareTo()
método você define a forma mais comum, ou natural, de comparar objetos dessa classe. Java já tem uma ordem natural. Por exemplo, Java sabe que as strings são frequentemente classificadas em ordem alfabética e os números aumentando o valor numérico. Portanto, se você chamar o sort()
método em uma lista de números ou strings, eles serão classificados. Se nosso programa costuma comparar e classificar carros por ano de fabricação, devemos definir a classificação natural para carros usando a Comparable<Car>
interface e ocompareTo()
método. Mas e se isso não for suficiente para nós? Vamos imaginar que nosso programa não seja tão simples. Na maioria dos casos, a classificação natural dos carros (que definimos para ser realizada por ano de fabricação) nos convém. Mas às vezes nossos clientes são aficionados por direção rápida. Se estivermos preparando um catálogo de carros para eles examinarem, os carros devem ser classificados por velocidade máxima. Por exemplo, suponha que precisamos classificar assim 15% do tempo. Isso claramente não é suficiente para definirmos a Car
classificação natural da classe por velocidade e não por ano de fabricação. Mas não podemos ignorar 15% dos nossos clientes. Então, o que fazemos? Outra interface vem em nosso auxílio aqui: Comparator
. Assim como Comparable
, é uma interface parametrizada. Qual é a diferença? Comparable
torna nossos objetos "comparáveis" e define sua ordem de classificação mais natural, ou seja, a ordem de classificação que será usada na maioria dos casos. Comparator
é uma interface de "comparação" separada. Se precisarmos implementar algum tipo de ordem de classificação especial, não precisamos entrar na Car
classe e alterar a lógica de compareTo()
. Em vez disso, podemos criar uma classe separada que implemente Comparator e ensiná-la a realizar a classificação de que precisamos!
import java.util.Comparator;
public class MaxSpeedCarComparator implements Comparator<Car> {
@Override
public int compare(Car o1, Car o2) {
return o1.getMaxSpeed() - o2.getMaxSpeed();
}
}
Como você pode ver, o nosso Comparator
é bem simples. Precisamos implementar apenas um método de interface: compare()
. Ele pega dois Car
objetos como entradas e compara suas velocidades máximas da maneira usual (por subtração). Assim como compareTo()
, retorna o an int
, e o princípio de comparação é o mesmo. Como usamos isso? É tudo direto:
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);
}
}
Saída do console:
[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}]
Simplesmente criamos um objeto comparador e passamos para o Collections.sort()
método junto com a lista a ser ordenada. Quando o sort()
método recebe um comparador, ele não usa a ordenação natural definida no método Car
da classe compareTo()
. Em vez disso, ele aplica o algoritmo de classificação definido pelo comparador passado para ele. Quais são as vantagens de fazer isso? Primeiro, a compatibilidade com o código existente. Criamos um novo método de classificação especial, mantendo o existente que será usado na maioria das vezes. Não tocamos na Car
aula. Era um Comparable
, e assim permanece:
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, flexibilidade. Podemos adicionar quantos algoritmos de ordenação quisermos. Por exemplo, podemos classificar os carros por cor, velocidade, peso ou quantas vezes um carro foi usado nos filmes do Batman. Tudo o que precisamos fazer é criar um arquivo Comparator
. É isso! Hoje você estudou dois mecanismos muito importantes que você usará frequentemente em projetos reais no trabalho. Mas, como você sabe, teoria sem prática não é nada. Agora é hora de consolidar seus conhecimentos e concluir algumas tarefas!
GO TO FULL VERSION