杰瑞科技汇

java hashcode equals

Of course! This is one of the most fundamental and important topics in Java. Understanding the relationship between hashCode() and equals() is crucial for writing correct and efficient Java code, especially when dealing with collections like HashMap, HashSet, and Hashtable.

java hashcode equals-图1
(图片来源网络,侵删)

Let's break it down.


The Golden Rule (The Contract)

First, and most importantly, you must understand the official contract defined in the Object class documentation. If you override one, you must override the other to adhere to this rule:

Whenever a.equals(b) is true, then a.hashCode() must return the same value as b.hashCode().

Let's dissect this.

java hashcode equals-图2
(图片来源网络,侵删)

The equals() Method

What is it for?

The equals() method is used to compare the content (or value) of two objects for logical equality. By default, the equals() method inherited from the Object class behaves like the operator—it checks if two references point to the exact same object in memory (reference equality).

public class Person {
    private String name;
    private int age;
    // Constructor, getters, setters...
}
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);
Person p3 = p1;
// Default equals() from Object class
System.out.println(p1.equals(p2)); // false (they are two different objects in memory)
System.out.println(p1.equals(p3)); // true (p3 is a reference to the same object as p1)

When to Override it?

You should override equals() when you need to define a custom notion of equality for your objects. For our Person class, we consider two Person objects equal if they have the same name and age, regardless of their memory address.

How to Override it Correctly?

A good equals() implementation typically follows these steps:

  1. Check for identity: Is this the exact same object as other? Use .
  2. Check for null: Is other null? If so, they can't be equal.
  3. Check for type: Is other an instance of the correct class? Use instanceof.
  4. Cast and compare fields: Cast other to your class and compare the relevant fields.
@Override
public boolean equals(Object o) {
    // 1. Check if it's the exact same object
    if (this == o) return true;
    // 2. Check if the other object is null or of a different class
    if (o == null || getClass() != o.getClass()) return false;
    // 3. Cast and compare the relevant fields
    Person person = (Person) o;
    return age == person.age && Objects.equals(name, person.name);
}

Note: Using Objects.equals() is safer as it handles null fields for you.

java hashcode equals-图3
(图片来源网络,侵删)

The hashCode() Method

What is it for?

The hashCode() method returns an integer, which is a numeric representation of the object's memory address. Its primary purpose is to support data structures that use hashing, like HashMap, HashSet, and Hashtable.

Think of it as a "quick pre-check." These data structures use the hash code to find a "bucket" where the object might be stored. This is much faster than searching through all elements.

The Default Behavior

By default, hashCode() returns a value derived from the object's memory address. This is why two different objects, even if they are "equal" by your custom definition, will have different hash codes if you don't override it.

Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);
// Default hashCode() from Object class
System.out.println(p1.hashCode()); // Some number based on p1's address
System.out.println(p2.hashCode()); // A different number based on p2's address
System.out.println(p1.equals(p2)); // false (by default)

When to Override it?

You must override hashCode() every time you override equals(). If you don't, you will break the fundamental contract and cause major bugs in hash-based collections.

How to Override it Correctly?

A good hashCode() implementation must:

  1. Be consistent: If an object's fields don't change, its hash code must not change.
  2. Be fast: It should be a quick computation.
  3. Obey the contract: If a.equals(b) is true, a.hashCode() must equal b.hashCode().

The best way to do this is to combine the hash codes of your object's significant fields.

@Override
public int hashCode() {
    // A simple but effective way to combine hash codes
    return Objects.hash(name, age);
}

The Objects.hash() utility method does this for you safely and effectively. It handles null fields and combines them in a way that minimizes collisions.


The Problem: Breaking the Contract

Let's see what happens when you override equals() but forget hashCode().

// BAD CODE! DO NOT DO THIS!
public class BadPerson {
    private String name;
    private int age;
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BadPerson badPerson = (BadPerson) o;
        return age == badPerson.age && Objects.equals(name, badPerson.name);
    }
    // NO hashCode() OVERRIDDEN!
}
public class Main {
    public static void main(String[] args) {
        BadPerson p1 = new BadPerson("Alice", 30);
        BadPerson p2 = new BadPerson("Alice", 30);
        // We define them as equal
        System.out.println("Are they equal? " + p1.equals(p2)); // true
        // Let's put them in a HashSet
        Set<BadPerson> people = new HashSet<>();
        people.add(p1);
        System.out.println("Set size after adding p1: " + people.size()); // 1
        // Now, let's check if p2 is in the set
        System.out.println("Does the set contain p2? " + people.contains(p2)); // false!
    }
}

Why does people.contains(p2) return false?

  1. HashSet calls p2.hashCode() to find the bucket where p2 should be.
  2. Since hashCode() wasn't overridden, it returns a value based on p2's memory address.
  3. It looks in that bucket and doesn't find p1 (because p1.hashCode() is different).
  4. It incorrectly concludes that p2 is not in the set.

This is a subtle but critical bug. The same issue occurs with HashMap.


The Solution: A Complete Example

Here is a Person class that correctly overrides both methods.

import java.util.Objects;
public final class Person { // final is a good practice
    private final String name;
    private final int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    // 1. Override equals()
    @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);
    }
    // 2. Override hashCode() to match the equals() contract
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
    // Optional: Override toString() for better logging/debugging
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Now, let's test it with a HashSet.

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("Alice", 30);
        Person p2 = new Person("Alice", 30);
        System.out.println("p1.equals(p2): " + p1.equals(p2)); // true
        System.out.println("p1.hashCode(): " + p1.hashCode()); // e.g., 123456789
        System.out.println("p2.hashCode(): " + p2.hashCode()); // e.g., 123456789 (Same!)
        Set<Person> people = new HashSet<>();
        people.add(p1);
        System.out.println("Set size after adding p1: " + people.size()); // 1
        System.out.println("Does the set contain p2? " + people.contains(p2)); // true!
    }
}

This time, everything works as expected. p2 is correctly identified as being in the set because its hash code matches p1's, and the subsequent equals() check confirms they are

分享:
扫描分享到社交APP
上一篇
下一篇