
📖 História: O Bug que Custou uma Tarde Inteira
Era uma sexta-feira à tarde na TechCorp quando Marina, desenvolvedora Java sênior, recebeu uma ligação urgente do Carlos, um desenvolvedor júnior da equipe.
Carlos: “Marina, preciso da sua ajuda! O sistema de autenticação está com um bug estranho. Às vezes funciona, às vezes não. Estou há 3 horas tentando resolver!”
Marina: “Calma, Carlos. Me mostra o código do método de validação.”
Carlos: “Aqui está o problema. Estou comparando as senhas assim:”
public boolean validarSenha(String senhaDigitada, String senhaArmazenada) {
return senhaDigitada == senhaArmazenada; // ❌ ERRO!
}
Marina: “Ahá! Encontrei o problema. Você está usando ==
para comparar Strings. Isso compara referências de memória, não o conteúdo das strings!”
Carlos: “Não entendi… Às vezes funciona!”
Marina: “Exato! Quando as strings vêm do pool de strings (literais), elas podem ter a mesma referência. Mas quando vêm de diferentes fontes, como entrada do usuário ou banco de dados, são objetos diferentes na memória.”
Carlos: “Então como resolvo?”
Marina: “Use o método equals()
. Ele compara o conteúdo das strings, não suas referências. Vamos criar um projeto completo para você entender melhor.”
🛠️ Criando o Projeto no IntelliJ IDEA
Passo 1: Novo Projeto Maven
- Abra o IntelliJ IDEA (versão 2024.1 ou superior)
- File > New > Project
- Selecione “Maven Archetype”
- Configure o projeto:
- Name:
java-comparacao-demo
- Location: Escolha seu diretório preferido
- Language: Java
- Build System: Maven
- JDK: 17 ou superior
- Add sample code: ✅ Marque esta opção
- Name:
Passo 2: Configuração do pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.exemplo</groupId>
<artifactId>java-comparacao-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.2</version>
</plugin>
</plugins>
</build>
</project>
📚 Conceitos Fundamentais Detalhados
1. Operador ==
(Comparação de Referência)
O operador ==
em Java funciona de forma diferente dependendo do tipo de dados:
🔹 Para Tipos Primitivos:
- Compara os valores diretamente
int
,double
,boolean
,char
, etc.- Armazenados na stack (pilha)
🔹 Para Objetos:
- Compara referências de memória (endereços)
- Verifica se ambas as variáveis apontam para o mesmo objeto na heap
- Não considera o conteúdo do objeto
2. Método equals()
(Comparação de Conteúdo)
O método equals()
é definido na classe Object
e pode ser sobrescrito:
🔹 Implementação Padrão:
- Comporta-se como
==
(compara referências) - Definido em
Object.equals()
🔹 Implementação Personalizada:
- Classes como
String
,Integer
,Double
sobrescrevem o método - Permite definir critérios customizados de igualdade
- Deve seguir o contrato do equals()
3. Pool de Strings – Conceito Crucial
O Java mantém um pool de strings para otimizar memória:
String str1 = "Java"; // Literal - vai para o pool
String str2 = "Java"; // Reutiliza a mesma referência do pool
String str3 = new String("Java"); // Cria novo objeto na heap
💻 Exemplo Prático Completo
Classe Principal: ComparacaoDemo.java
package com.exemplo;
import java.util.Objects;
import java.util.Scanner;
/**
* Demonstração prática das diferenças entre == e equals() em Java
*
* @author Marina Silva
* @version 1.0
*/
public class ComparacaoDemo {
public static void main(String[] args) {
System.out.println("=== DEMONSTRAÇÃO: == vs equals() ===\n");
// Demonstrações individuais
demonstrarPrimitivos();
demonstrarStrings();
demonstrarObjetos();
demonstrarCasoReal();
System.out.println("\n=== FIM DA DEMONSTRAÇÃO ===");
}
/**
* Demonstra comparação com tipos primitivos
*/
private static void demonstrarPrimitivos() {
System.out.println("🔢 TIPOS PRIMITIVOS:");
System.out.println("-------------------");
int numero1 = 42;
int numero2 = 42;
int numero3 = 50;
System.out.printf("numero1 = %d, numero2 = %d, numero3 = %d%n", numero1, numero2, numero3);
System.out.printf("numero1 == numero2: %b%n", numero1 == numero2);
System.out.printf("numero1 == numero3: %b%n", numero1 == numero3);
double preco1 = 19.99;
double preco2 = 19.99;
System.out.printf("preco1 == preco2: %b%n", preco1 == preco2);
System.out.println();
}
/**
* Demonstra comparação com Strings
*/
private static void demonstrarStrings() {
System.out.println("📝 STRINGS:");
System.out.println("----------");
// Strings literais (pool)
String str1 = "Java";
String str2 = "Java";
System.out.println("String literais:");
System.out.printf("str1 == str2: %b%n", str1 == str2);
System.out.printf("str1.equals(str2): %b%n", str1.equals(str2));
// Strings com new (objetos diferentes)
String str3 = new String("Java");
String str4 = new String("Java");
System.out.println("\nString com new:");
System.out.printf("str3 == str4: %b%n", str3 == str4);
System.out.printf("str3.equals(str4): %b%n", str3.equals(str4));
// Comparação entre literal e new
System.out.println("\nLiteral vs New:");
System.out.printf("str1 == str3: %b%n", str1 == str3);
System.out.printf("str1.equals(str3): %b%n", str1.equals(str3));
// Demonstração da identidade dos objetos
System.out.println("\nIdentidade dos objetos:");
System.out.printf("str1.hashCode(): %d%n", str1.hashCode());
System.out.printf("str2.hashCode(): %d%n", str2.hashCode());
System.out.printf("str3.hashCode(): %d%n", str3.hashCode());
System.out.printf("System.identityHashCode(str1): %d%n", System.identityHashCode(str1));
System.out.printf("System.identityHashCode(str3): %d%n", System.identityHashCode(str3));
System.out.println();
}
/**
* Demonstra comparação com objetos personalizados
*/
private static void demonstrarObjetos() {
System.out.println("👤 OBJETOS PERSONALIZADOS:");
System.out.println("-------------------------");
// Criando usuários
Usuario usuario1 = new Usuario("carlos.silva", "Carlos Silva", 28);
Usuario usuario2 = new Usuario("carlos.silva", "Carlos Silva", 28);
Usuario usuario3 = usuario1; // mesma referência
Usuario usuario4 = new Usuario("marina.santos", "Marina Santos", 32);
System.out.println("Usuários criados:");
System.out.println("usuario1: " + usuario1);
System.out.println("usuario2: " + usuario2);
System.out.println("usuario4: " + usuario4);
System.out.println("\nComparações:");
System.out.printf("usuario1 == usuario2: %b (objetos diferentes, mesmo conteúdo)%n", usuario1 == usuario2);
System.out.printf("usuario1.equals(usuario2): %b%n", usuario1.equals(usuario2));
System.out.printf("usuario1 == usuario3: %b (mesma referência)%n", usuario1 == usuario3);
System.out.printf("usuario1.equals(usuario3): %b%n", usuario1.equals(usuario3));
System.out.printf("usuario1.equals(usuario4): %b (conteúdo diferente)%n", usuario1.equals(usuario4));
System.out.println();
}
/**
* Demonstra caso real - sistema de autenticação
*/
private static void demonstrarCasoReal() {
System.out.println("🔐 CASO REAL - SISTEMA DE AUTENTICAÇÃO:");
System.out.println("-------------------------------------");
// Simula diferentes fontes de string
String senhaArmazenada = "123456"; // Literal (pool)
String senhaDigitada = new String("123456"); // Vem de input do usuário
String senhaInvalida = "654321";
SistemaAutenticacao sistema = new SistemaAutenticacao();
System.out.println("Testando validação de senhas:");
System.out.printf("Senha armazenada: '%s'%n", senhaArmazenada);
System.out.printf("Senha digitada: '%s'%n", senhaDigitada);
// Método incorreto (usando ==)
boolean loginIncorreto = sistema.validarSenhaIncorreto(senhaDigitada, senhaArmazenada);
System.out.printf("Login com == (INCORRETO): %b%n", loginIncorreto);
// Método correto (usando equals)
boolean loginCorreto = sistema.validarSenhaCorreto(senhaDigitada, senhaArmazenada);
System.out.printf("Login com equals() (CORRETO): %b%n", loginCorreto);
// Teste com senha inválida
boolean loginInvalido = sistema.validarSenhaCorreto(senhaInvalida, senhaArmazenada);
System.out.printf("Login com senha inválida: %b%n", loginInvalido);
}
}
/**
* Classe que representa um usuário do sistema
*/
class Usuario {
private String username;
private String nomeCompleto;
private int idade;
public Usuario(String username, String nomeCompleto, int idade) {
this.username = username;
this.nomeCompleto = nomeCompleto;
this.idade = idade;
}
/**
* Implementação correta do equals() seguindo o contrato
*/
@Override
public boolean equals(Object obj) {
// 1. Verificação de referência (otimização)
if (this == obj) return true;
// 2. Verificação de null
if (obj == null) return false;
// 3. Verificação de classe
if (getClass() != obj.getClass()) return false;
// 4. Cast e comparação de campos
Usuario usuario = (Usuario) obj;
return idade == usuario.idade &&
Objects.equals(username, usuario.username) &&
Objects.equals(nomeCompleto, usuario.nomeCompleto);
}
/**
* Implementação do hashCode() - OBRIGATÓRIO quando equals() é sobrescrito
*/
@Override
public int hashCode() {
return Objects.hash(username, nomeCompleto, idade);
}
@Override
public String toString() {
return String.format("Usuario{username='%s', nome='%s', idade=%d}",
username, nomeCompleto, idade);
}
// Getters
public String getUsername() { return username; }
public String getNomeCompleto() { return nomeCompleto; }
public int getIdade() { return idade; }
}
/**
* Sistema de autenticação para demonstrar o problema
*/
class SistemaAutenticacao {
/**
* Método INCORRETO - usa == para comparar strings
*/
public boolean validarSenhaIncorreto(String senhaDigitada, String senhaArmazenada) {
return senhaDigitada == senhaArmazenada; // ❌ ERRO!
}
/**
* Método CORRETO - usa equals() para comparar strings
*/
public boolean validarSenhaCorreto(String senhaDigitada, String senhaArmazenada) {
if (senhaDigitada == null || senhaArmazenada == null) {
return false;
}
return senhaDigitada.equals(senhaArmazenada); // ✅ CORRETO!
}
}
🧪 Teste Unitário (JUnit 5)
Crie o arquivo src/test/java/com/exemplo/ComparacaoTest.java
:
package com.exemplo;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.BeforeEach;
import static org.junit.jupiter.api.Assertions.*;
@DisplayName("Testes de Comparação: == vs equals()")
class ComparacaoTest {
private SistemaAutenticacao sistema;
@BeforeEach
void setUp() {
sistema = new SistemaAutenticacao();
}
@Test
@DisplayName("Deve comparar strings literais corretamente")
void testComparacaoStringsLiterais() {
String str1 = "Java";
String str2 = "Java";
String str3 = new String("Java");
// Literais têm mesma referência
assertTrue(str1 == str2, "Strings literais devem ter mesma referência");
assertTrue(str1.equals(str2), "Strings literais devem ser iguais por conteúdo");
// new String cria objeto diferente
assertFalse(str1 == str3, "String literal vs new String devem ter referências diferentes");
assertTrue(str1.equals(str3), "String literal vs new String devem ser iguais por conteúdo");
}
@Test
@DisplayName("Deve comparar objetos Usuario corretamente")
void testComparacaoUsuarios() {
Usuario user1 = new Usuario("test", "Test User", 25);
Usuario user2 = new Usuario("test", "Test User", 25);
Usuario user3 = user1;
Usuario user4 = new Usuario("other", "Other User", 30);
// Objetos diferentes, mesmo conteúdo
assertFalse(user1 == user2, "Objetos diferentes devem ter referências diferentes");
assertTrue(user1.equals(user2), "Objetos com mesmo conteúdo devem ser iguais");
// Mesma referência
assertTrue(user1 == user3, "Mesma referência deve ser igual com ==");
assertTrue(user1.equals(user3), "Mesma referência deve ser igual com equals()");
// Conteúdo diferente
assertFalse(user1.equals(user4), "Objetos com conteúdo diferente devem ser diferentes");
}
@Test
@DisplayName("Deve validar sistema de autenticação")
void testSistemaAutenticacao() {
String senhaArmazenada = "123456";
String senhaDigitada = new String("123456");
String senhaInvalida = "654321";
// Método incorreto pode falhar
assertFalse(sistema.validarSenhaIncorreto(senhaDigitada, senhaArmazenada),
"Método incorreto deve falhar com strings de fontes diferentes");
// Método correto sempre funciona
assertTrue(sistema.validarSenhaCorreto(senhaDigitada, senhaArmazenada),
"Método correto deve funcionar independente da fonte das strings");
// Senha inválida
assertFalse(sistema.validarSenhaCorreto(senhaInvalida, senhaArmazenada),
"Senha inválida deve retornar false");
}
@Test
@DisplayName("Deve tratar valores null corretamente")
void testValoresNull() {
// Teste com valores null
assertFalse(sistema.validarSenhaCorreto(null, "123456"),
"Senha null deve retornar false");
assertFalse(sistema.validarSenhaCorreto("123456", null),
"Senha armazenada null deve retornar false");
assertFalse(sistema.validarSenhaCorreto(null, null),
"Ambas senhas null devem retornar false");
}
@Test
@DisplayName("Deve validar contrato equals() e hashCode()")
void testContratoEqualsHashCode() {
Usuario user1 = new Usuario("test", "Test User", 25);
Usuario user2 = new Usuario("test", "Test User", 25);
Usuario user3 = new Usuario("other", "Other User", 30);
// Reflexividade
assertTrue(user1.equals(user1), "equals() deve ser reflexivo");
// Simetria
assertTrue(user1.equals(user2), "equals() deve ser simétrico");
assertTrue(user2.equals(user1), "equals() deve ser simétrico");
// Consistência do hashCode
assertEquals(user1.hashCode(), user2.hashCode(),
"Objetos iguais devem ter mesmo hashCode");
// Objetos diferentes podem ter hashCodes diferentes
assertNotEquals(user1.hashCode(), user3.hashCode(),
"Objetos diferentes devem ter hashCodes diferentes");
// Null safety
assertFalse(user1.equals(null), "equals(null) deve retornar false");
}
}
📋 Executando o Projeto
- Compile o projeto:
mvn clean compile
- Execute a classe principal:
mvn exec:java -Dexec.mainClass="com.exemplo.ComparacaoDemo"
- Execute os testes:
mvn test
- Execute testes com relatório detalhado:
mvn test -Dtest=ComparacaoTest
🎯 Boas Práticas e Regras Importantes
1. Contrato do equals()
Ao sobrescrever equals()
, deve-se seguir estas regras:
- Reflexiva:
x.equals(x)
deve retornartrue
- Simétrica:
x.equals(y)
deve retornar o mesmo quey.equals(x)
- Transitiva: Se
x.equals(y)
ey.equals(z)
, entãox.equals(z)
- Consistente: Múltiplas chamadas devem retornar o mesmo resultado
- Null-safe:
x.equals(null)
deve retornarfalse
2. Sempre sobrescrever hashCode()
@Override
public int hashCode() {
return Objects.hash(campo1, campo2, campo3);
}
3. Verificações de Segurança
// Sempre verificar null antes de chamar equals()
if (str1 != null && str1.equals(str2)) {
// código seguro
}
// Ou usar Objects.equals() para verificação automática de null
if (Objects.equals(str1, str2)) {
// código seguro
}
4. Comparação de Arrays
int[] array1 = {1, 2, 3};
int[] array2 = {1, 2, 3};
// ❌ INCORRETO - compara referências
System.out.println(array1 == array2); // false
System.out.println(array1.equals(array2)); // false
// ✅ CORRETO - compara conteúdo
System.out.println(Arrays.equals(array1, array2)); // true
🔍 Casos Especiais e Pegadinhas
1. Autoboxing e Pool de Inteiros
Integer num1 = 127;
Integer num2 = 127;
Integer num3 = 128;
Integer num4 = 128;
System.out.println(num1 == num2); // true (pool -128 a 127)
System.out.println(num3 == num4); // false (fora do pool)
2. Strings e StringBuilder
String str = "Hello";
StringBuilder sb = new StringBuilder("Hello");
// ❌ INCORRETO - tipos diferentes
// str.equals(sb); // false
// ✅ CORRETO - converte para String
str.equals(sb.toString()); // true
🎯 Resumo Final
Aspecto | `==` | `equals()` |
---|---|---|
**Tipos Primitivos** | Compara valores | Não aplicável |
**Objetos** | Compara referências | Compara conteúdo |
**Strings** | Referência (cuidado com pool) | Conteúdo |
**Performance** | Mais rápido | Pode ser mais lento |
**Personalização** | Não pode ser alterado | Pode ser sobrescrito |
💡 Dicas Finais
- Use
equals()
para comparar conteúdo de objetos - Use
==
apenas para primitivos ou verificação de referência - Sempre implemente
hashCode()
quando sobrescreverequals()
- Use
Objects.equals()
para evitar NullPointerException - Teste sempre seus métodos
equals()
com diferentes cenários - Com JUnit 5, use anotações como
@DisplayName
para testes mais descritivos - Aproveite as novas assertions do JUnit 5 para mensagens de erro mais claras
Marina: “Agora entendeu, Carlos? O problema era que você estava comparando referências em vez de conteúdo! E veja como o JUnit 5 torna nossos testes mais expressivos e organizados.”
Carlos: “Perfeito! Agora vou usar equals()
para comparar strings e implementar corretamente o equals()
e hashCode()
nas minhas classes. E adorei as melhorias do JUnit 5! Muito obrigado pela explicação detalhada!”
Marina: “De nada! Lembre-se sempre: quando em dúvida sobre comparação de objetos, use equals()
. E não se esqueça do hashCode()
quando sobrescrever o equals()
. O JUnit 5 vai te ajudar muito a criar testes mais robustos e legíveis!”