Comparing objects for equality in Java requires understanding the difference between the == operator and the equals() method. While == checks for reference equality, equals() checks for value equality.

This article provides an in-depth guide on when and how to use these approaches properly, best practices for implementing equality, and how to avoid common pitfalls.

How == and equals() Differ

The == operator and equals() method differ in these key ways:

  • == checks if references refer to the same exact object instance in memory
  • equals() checks if two objects have the same contents
  • == cannot be overridden, while equals() is intended to be overridden

Consider this code example:

String s1 = new String("foo");
String s2 = new String("foo");

if (s1 == s2) {
   // False, different objects
}

if (s1.equals(s2)) {
   // True, same value  
}

While s1 and s2 contain the same string content, they refer to two distinct String instances. So == returns false while equals() returns true.

Understanding == and Interning

It‘s important to note that some classes like String use interning to reduce duplication. String literals and constant values are stored in a special memory pool on the JVM.

So this comparison returns true:

String s1 = "foo";
String s2 = "foo"; 

if (s1 == s2) {
   // True, same instance
}

Whereas this still returns false since the compiler cannot infer these are identical constant values:

String s1 = new String("foo"); 
String s2 = new String("foo");

if (s1 == s2) {
   // False, distinct objects
}

So always keep the impact of interning in mind when using == with certain object types.

Overriding equals() and hashCode()

Every class inherits an equals() method from the Object class. This default implementation simply checks == equality:

public boolean equals(Object other) {
    return this == other;
}

This is almost never the desired behaviour!

That‘s why custom classes should override equals() and also override hashCode() for proper equality checking, ex:

public class Person {
    String name;

    public boolean equals(Object other) {
       // Check for null, type matching, cast..  

       Person p = (Person) other; 
       return this.name.equals(p.name); 
    }

    public int hashCode() {
       return name.hashCode(); 
    }
}

Now equals() checks if two Person instances have matching names. And hashCode() corresponds to that same logic.

Consistent Behavior for equals() and compareTo()

The compareTo() method is used to define a natural order for objects so they can be sorted:

sgn(x.compareTo(y)) == 0  -> equals() should return true  

x.equals(y) == true -> sgn(compareTo()) == 0   

Where sgn() returns the sign of the expression:

  • sgn(x) = -1 if x < 0
  • sgn(x) = 0 if x = 0
  • sgn(x) = +1 if x > 0

Classes that have a natural ordering should follow these rules to ensure objects Equals implies same sort order.

Checking for Null Values

A common pitfall is neglecting to check for null values in custom equality logic:

// Buggy code! May throw NPE!
public boolean equals(Object other) {

  // Forgot to check other for null!
  return this.value.equals(other.value); 

}

// Fix:
public boolean equals(Object other) {

  // Check for null!
  if (other == null) { return false; }

  return this.value.equals(other.value);
}

Always checking for null avoids null pointer exceptions and unforeseen behavior.

Common Pitfalls to Avoid

Some other common pitfalls when overriding equals include:

1. Forgetting HashCodeContract

Every class that overrides equals() must also override hashCode() for hash collections to operate properly.

2. Comparing Incompatible Types

Accidental mixing of incompatible types in equality checks causes ugly class cast exceptions:

// Oops! Incompatible type!
if (myObject.equals(anotherType)) {

}

Using a common base class or interface for compare targets avoids this.

3. Allowing Transitive Inequality

Oops:

a.equals(b) // true
b.equals(c) // true  
a.equals(c) // false! Not good!

This breaks the transitive nature of equality. Watch out for it!

Apache Commons Lang Utilities

The Apache Commons Lang library provides useful helper classes for implementing hashCode() and equals():

public class Person {

  String name;

  public boolean equals(Object other) {
     return EqualsBuilder.reflectionEquals(this, other);
  }

  public int hashCode() {
    return HashCodeBuilder.reflectionHashCode(17, 37, this); 
  } 
}

This handles null checks, type safety, and consistency automatically!

Performance Implications

Equality checks have performance implications to keep in mind:

  • == is faster than equals()
  • Calling user-defined equals() causes virtual method invocation
  • Lots of calls to equals() or hashCode() hit performance

Thus, its ideal to use == for performance when possible. And implementing custom equals()/hashCode() logic efficiently.

Bug Rates in Production

A recent study across 500+ codebases found 7-15% of reported bugs stemmed from violated equals/hashCode contracts! This indicates how common such issues arise.

Testing Equality and Hash Codes

Some guidelines for testing equality correctly:

1. Test symmetry:

obj1.equals(obj2) == obj2.equals(obj1)

2. Test transitivity:

!obj1.equals(obj2) && obj2.equals(obj3) 
   -> !obj1.equals(obj3) 

3. Test inheritance handling

4. Check null handling

And run tests against hashCode() as well!

This helps catch any logical errors.

Equality in Inheritance Hierarchies

Here are best practices for equals() and inheritance:

  • Don‘t override equals() only to ignore superclass fields
    • Violates substitutability – a subclass instance may compare equal to a superclass instance
  • Always override hashCode() as well
    • Otherwise subclasses break hashed collections of superclass objects
  • Include a getClass() check before comparing contents
    • Ensures only objects of identical runtime type are equal

Specialized Equality Semantics

Sometimes equality has special semantics:

Canonical equivalence – different objects representing the same abstract entity are equal, likeBigDecimals representing 4.5.

Structural equality – two objects with identical internal structure compare equal, like mathematical sets {1, 2, 3}. Useful in data modelling.

Numeric equivalence – numeric wrapper classes provide type conversions allowing equality checks across primitive types.

Specialized domain logic applies here.

Integrating Equality with ORMs

Equality plays a key role in persistence and ORM frameworks like Hibernate and JPA:

  • The primary key field(s) indicate object uniqueness
  • Retrieving objects from database hinges on correct equals() logic
  • Equality checks used when persisting entities

So properly overriding equals() and hashCode() integrates cleanly with ORMs.

Conclusion

The == operator and equals() method differ in how they compare objects in Java.

Following best practices for overriding equals() and hashCode() enables robust equality checking. And avoiding common pitfalls prevents nasty bugs!

With a deep understanding of object equality in Java, you can compare objects properly based on your specific needs.

Similar Posts