Funções em Java: compreendendo a função equals
Introdução
Em muitas situações durante o desenvolvimento de software em Java, surge a necessidade de comparar objetos para determinar igualdade. Para isso, a função equals desempenha um papel crucial. Dessa forma, abordarei nesse artigo o funcionamento desse método, além de esclarecer uma dúvida bastante comum sobre seu uso.
Sobre a função
O método equals pertence à classe Object e é utilizado para determinar se dois objetos são idênticos ou não. Como, com exceção dos tipos primitivos, tudo em Java é um objeto (herda da classe Object), todas as classes possuem esse método.
Além disso, a função equals possui uma implementação padrão definida pelo Java e que geralmente é sobrescrita (override) para ser utilizada nas classes criadas pelo usuário seguindo algum critério de comparação.
Funcionamento
Implementação padrão
De acordo com a documentação Java, a implementação padrão do método equals é:
public boolean equals(Object obj) {
return (this == obj);
}
Isso significa que, se não for sobrescrito, a comparação será feita considerando a igualdade por endereço de memória dos objetos. É possível conferir isso com o exemplo a seguir.
Considere a classe Person (pessoa):
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
Observe o que ocorre quando criamos dois objetos dessa classe e tentamos compará-los sem ter sobrescrito equals:
Person person1 = new Person('Alice', 25);
Person person2 = new Person('Alice', 25);
System.out.println(person1.equals(person2));
# Output:
# false
A implementação padrão é utilizada e o resultado é falso porque new cria um novo objeto com endereço de memória diferente dos demais, isto é, o endereço de memória de person1 é diferente do endereço de memória de person2.
Implementação personalizada
Obviamente não faz sentido algum comparar uma pessoa com outra considerando o endereço de memória. Digamos que o critério de comparação seja o nome e a idade. Deve-se, então, sobrescrever o método equals especificando esses critérios.
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
Com isso, a comparação feita anteriormente será verdadeira.
System.out.println(person1.equals(person2));
# Output:
# true
Explicando o código
if (this == o) return true; verifica se o objeto atual (this) é o mesmo objeto que o objeto passado como argumento (o). Se forem a mesma instância na memória, não é necessário fazer mais comparações, então retorna true.
if (o == null || getClass() != o.getClass()) return false; verifica se o objeto passado como argumento (o) é nulo ou se ele não é uma instância da mesma classe que this. Se qualquer uma dessas condições for verdadeira, os objetos não podem ser iguais, então retorna false.
Person person = (Person) o; faz um downcast do objeto o para a classe Person, pois o é do tipo Object. Isso é seguro porque verificamos anteriormente se o é uma instância de Person.
return age == person.age && Objects.equals(name, person.name); compara os campos age dos objetos this e o, verificando se são iguais. Além disso, compara os campos name usando Objects.equals() para verificar se eles são iguais, levando em consideração a possibilidade de name ser nulo em um dos objetos.
Você deve ter notado duas questões importantes nesse trecho de código: a comparação com == e o uso de Objects.equals. Não se preocupe, tudo será explicado:
Objects.equals() é um método da classe utilitária Objects que geralmente é utilizado para comparar atributos de classes, pois considera a igualdade de null como verdadeira. Sua implementação, de acordo com a documentação Java é:
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
E quanto a age == person.age ? Bom, para isso, é preciso lembrar do que foi dito no início do artigo. O método equals está disponível apenas em objetos, o que não é o caso dos tipos primitivos, que não são objetos nem possuem métodos. Dessa forma, para comparar tipos primitivos, o == é utilizado.
O uso do == é gerador de uma dúvida bastante comum que em breve esclarecerei.
Características
De acordo com a documentação Java, a implementação de equals deve satisfazer as seguintes diretrizes para objetos não nulos:
- É reflexivo: para qualquer valor de referência x, x.equals() deve retornar true;
- É simétrico: para qualquer valor de referência x e y, x.equals(y) deve retornar true se, e somente se, y.equals(x) retornar true.
- É transitivo: para qualquer valor de referência de x, y e z, se x.equals(y) retornar true e y.equals(z) também retornar true, então, x.equals(z) deve retornar true.
- É consistente: para qualquer valor de referência de x e y, múltiplas chamadas de x.equals(y) retornarão consistentemente true ou consistentemente false, contanto que nenhuma informação usada nas comparações dos objetos de equals tenha sido alterada.
- Para qualquer valor de referência x, x.equals(null) deve retornar false.
Uma dúvida comum
Uma dúvida bastante comum entre desenvolvedores Java é a diferença entre a função equals e o uso do == .
Em se tratando de objetos, a diferença é simples. Com uma implementação personalizada de equals, equals realiza a comparação de acordo com o critério definido enquanto == realiza a comparação considerando o endereço de memória dos objetos.
Com a implementação padrão, o uso de equals é equivalente ao uso de ==, isto é, compara considerando o endereço de memória dos objetos.
Em se tratando de tipos primitivos, equals não é disponível, e a comparação é feita considerando o valor e utilizando == .
Considerações
Para facilitar o uso da linguagem, algumas classes bastante utilizadas já possuem uma implementação personalizada de equals, como String, Integer, Double, Long, entre outras.
A título de curiosidade, essa é a utilizada pela classe String no Java 17:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
return (anObject instanceof String aString)
&& (!COMPACT_STRINGS || this.coder == aString.coder)
&& StringLatin1.equals(value, aString.value);
}
Além disso, cabe ressaltar que uma prática quase obrigatória é a implementação personalizada da função equals junto com a da função hashCode, mas que não abordarei neste artigo pois foge do escopo.
Conclusão
Quando se pretende comparar objetos, entender o funcionamento da função equals, suas diferenças e particularidades é fundamental para se tornar um desenvolvedor Java que domina a linguagem.
Referências
https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#equals-java.lang.Object-
https://docs.oracle.com/javase/8/docs/api/java/util/Objects.html#equals-java.lang.Object-java.lang.Object-
https://ioflood.com/blog/dot-equals-method-java/#:~:text=equals()%20method%20is%20primarily,any%20two%20objects%20in%20Java.
https://www.devmedia.com.br/sobrescrevendo-o-metodo-equals-em-java/26484
https://medium.com/@erayaraz10/when-to-use-equals-and-when-to-use-in-java-best-practices-6915b3fc9983