Java object equality
Java object equality
In Java, object equality refers to the state where two objects are considered the same. There are two primary ways to check for equality: reference equality (using ==) and logical equality (using equals()).
Reference Equality (==)
The == operator compares the memory addresses of two objects. This means it checks if two object references point to the exact same object in memory. If they do, the expression object1 == object2 evaluates to true; otherwise, it’s false. For primitive data types (like int, char, double), the == operator compares their actual values.
For example:
String a = new String("hello");
String b = new String("hello");
String c = a;
System.out.println(a == b); // false, as 'a' and 'b' are different objects in memory
System.out.println(a == c); // true, as 'a' and 'c' both refer to the same object
Logical Equality (equals())
The equals() method is used to determine if two objects are logically equal, meaning they have the same content or state. The default equals() method, inherited from the Object class, behaves just like the == operator—it checks for reference equality.
To properly compare the contents of two objects, you must override the equals() method in your class. When overriding equals(), it’s a best practice to also override the hashCode() method, as contractually required by the Java API. The contract states that if two objects are equal according to the equals() method, they must have the same hash code.
For example, a custom equals() method for a Book class would check if the title, author, and ISBN are the same:
public class Book {
private String title;
private String author;
private String isbn;
// constructor and getters...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Book book = (Book) o;
return Objects.equals(title, book.title) &&
Objects.equals(author, book.author) &&
Objects.equals(isbn, book.isbn);
}
}
This overridden method allows you to compare two Book objects and determine if they represent the same book, regardless of whether they are the same instance in memory.
java equals and hashcode methods
In Java, the equals and hashCode methods are essential for comparing objects and ensuring they behave correctly in hash-based data structures. They are defined in the java.lang.Object class, which is the superclass of all Java classes.
The equals() Method
The equals() method determines if two objects are considered equal in terms of their content or logical state. The default implementation of equals() in Object simply checks for reference equality using the == operator, meaning it returns true only if both variables refer to the exact same object in memory.
You should override this method whenever you need to define a custom notion of equality for your class. This is common for classes representing data, such as a Person with name and age fields. To correctly override equals(), you must follow these five rules (the equals contract):
- Reflexive:
x.equals(x)must betrue. - Symmetric: If
x.equals(y)istrue, theny.equals(x)must also betrue. - Transitive: If
x.equals(y)istrueandy.equals(z)istrue, thenx.equals(z)must also betrue. - Consistent: Multiple invocations of
x.equals(y)must consistently return the same result, assuming no information used inequalscomparisons on the objects is modified. - Nullity:
x.equals(null)must always befalse.
Here’s an example of a simple equals() override:
@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);
}
The hashCode() Method
The hashCode() method returns an integer hash code value for the object. This value is used primarily by hash-based collections like HashSet, HashMap, and Hashtable to quickly store and retrieve objects. The hash code helps distribute objects into different buckets within the collection, which speeds up lookups.
The hashCode contract is a set of rules that must be followed if you override hashCode():
- If two objects are equal according to the
equals()method, they must have the same hash code. - If an object’s state isn’t changed in a way that affects its
equals()comparison,hashCode()must return the same value consistently. - Unequal objects do not need to have different hash codes, although having different hash codes for unequal objects improves the performance of hash collections.
Why override both equals() and hashCode()?
Failing to override hashCode() when you override equals() can lead to significant bugs, especially when using hash collections.
For example, if you add an object to a HashSet, its hashCode() is used to determine its bucket. If you later create a new, but equal, object and try to check if it exists in the HashSet using contains(), the contains() method will call hashCode() on the new object. If hashCode() isn’t overridden, it will return a different value (based on memory address), causing the contains() method to look in the wrong bucket and incorrectly return false.
The golden rule: If you override equals(), you must override hashCode(). A simple way to generate a good hash code is to use the fields that are used in the equals() comparison. The Objects.hash() utility method is a convenient way to do this.
Here is the corresponding hashCode() method for the Person class:
@Override
public int hashCode() {
return Objects.hash(name, age);
}