Java Comparison Mechanics: `==` Operator versus `equals()` Method
In Java, determining equality involves distinct mechanisms depending on whether one is comparing primitive values or object references. The == operator and the equals() method serve different purposes in this context.
The == Operator
The == operator functions as a binary comparison mechanism. For primitive data types (such as int, char, boolean), it evaluates whether the actual values stored in the variables are identical.
int val1 = 100;
int val2 = 100;
System.out.println(val1 == val2); // true, since values match
When applied to objects, == performs a reference comparison. It checks if two variables point to the exact same memory address in the heap. Even if two objects contain identical atttributes, == returns false if they are distinct instances.
String str1 = new String("demo");
String str2 = new String("demo");
System.out.println(str1 == str2); // false, distinct memory locations
String str3 = "demo";
String str4 = "demo";
System.out.println(str3 == str4); // true, both refer to the same string constant pool instance
The equals() Method
The equals() method, defined in the Object class, is intended for content comparison. By default, the Object class implementation simply uses the == operator, effectively comparing references.
public boolean equals(Object obj) {
return (this == obj);
}
However, many classes in the Java Standard Library, such as String, Integer, and Date, override this method to compare the actual data within the objects.
String data1 = new String("sample");
String data2 = new String("sample");
System.out.println(data1.equals(data2)); // true, character sequences match
Integer int1 = new Integer(50);
Integer int2 = new Integer(50);
System.out.println(int1.equals(int2)); // true, integer values match
When overriding equals(), one must adhere to a specific contract to ensure consistent behavior:
- Reflexive: An object must equal itself.
- Symmetric: If
a.equals(b)is true, thenb.equals(a)must be true. - Transitive: If
a.equals(b)andb.equals(c)are true, thena.equals(c)must be true. - Consistent: Multiple invocations must return the same result, provided the object state remains unchanged.
- Non-null: Any non-null reference must return false when compared to null.
Implementation Example
Below is an implementation of a User class that overrides equals to compare based on the id and username fields. The hashCode() method is also updated to maintain the contract that equal objects must have equal hash codes.
import java.util.Objects;
public class User {
private final int id;
private final String username;
public User(int id, String username) {
this.id = id;
this.username = username;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || this.getClass() != obj.getClass()) {
return false;
}
User other = (User) obj;
return this.id == other.id && Objects.equals(this.username, other.username);
}
@Override
public int hashCode() {
return Objects.hash(id, username);
}
public static void main(String[] args) {
User userA = new User(101, "admin");
User userB = new User(101, "admin");
User userC = userA;
System.out.println(userA == userB); // false, different references
System.out.println(userA.equals(userB)); // true, identical content
System.out.println(userA == userC); // true, same reference
System.out.println(userA.equals(userC)); // true, same reference implies same content
}
}