Skip to Content
Course content

39: The Object Class: equals, hashCode, toString

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Every single class you write in Java implicitly inherits from the Object class. It's the root of the entire hierarchy. Most of the time, you don't think about it, but there are three methods in Object that you'll find yourself overriding constantly. If you don't, you're going to run into bugs that are a nightmare to debug because the code looks correct, but the behavior is just... wrong.

Why is my comparison returning false even though the data is identical?

This is the classic "gotcha." You create two Product objects with the same ID and name, you compare them using ==, and Java tells you they aren't equal. That's because == checks for reference equalityβ€”it's asking, "Are these two variables pointing to the exact same spot in memory?"

If you want to check if two objects are "logically" the same, you have to override equals(). Here is how I usually handle this for a simple data object:

public class Product {
    private final int id;
    private final String name;

    public Product(int id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true; // Same memory address? Easy win.
        if (o == null || getClass() != o.getClass()) return false; // Null or different type? Not equal.
        
        Product product = (Product) o;
        return id == product.id && java.util.Objects.equals(name, product.name);
    }
}

Notice I used java.util.Objects.equals() for the name. I do this because if name is null, a standard name.equals() would throw a NullPointerException. Always be defensive.

Do I really have to override hashCode if I already did equals?

Short answer: Yes. Absolutely. If you don't, you've just broken every hash-based collection in Java, including HashMap and HashSet.

Here is the deal: these collections use a "bucket" system. When you put an object into a HashSet, Java calls hashCode() to decide which bucket to put it in. When you try to find that object later, it calls hashCode() again to find the bucket, and then it uses equals() to find the specific object in that bucket.

If two objects are equal according to equals(), they must return the same hash code. If they don't, Java will look in the wrong bucket and tell you the object isn't there, even though it's sitting right there in a different bucket. I usually keep it simple using the Objects.hash() utility:

@Override
public int hashCode() {
    return java.util.Objects.hash(id, name);
}

Why am I seeing weird gibberish like "Product@15db9742" in my logs?

That "gibberish" is the default toString() implementation. By default, Object.toString() prints the class name, an '@' symbol, and the unsigned hexadecimal representation of the hash code. It's practically useless for debugging.

I always override toString() as soon as I create a domain class. It makes your logs readable and saves you from having to manually print every single field when you're trying to figure out why a variable is null. Just keep it concise:

@Override
public String toString() {
    return "Product{id=" + id + ", name='" + name + "'}";
}

Now, instead of a random memory address, you get something you can actually use to diagnose a problem.




πŸ“‹ Practical Task

Fixing the Product Catalog Duplicate Bug

You are working on an e-commerce inventory system. The system uses a HashSet to ensure that no duplicate products are added to the catalog. However, the lead developer noticed that the same product is being added multiple times, bypassing the duplicate check.

Below is the current Product class. It has a broken implementation of equals and is missing hashCode entirely. Fix the class so that two Product objects with the same sku are considered equal, and the HashSet correctly prevents duplicates.

import java.util.*;

public class Product {
    private String sku;
    private String description;

    public Product(String sku, String description) {
        this.sku = sku;
        this.description = description;
    }

    // BUG: This only checks if they are the same instance in memory
    @Override
    public boolean equals(Object o) {
        return this == o; 
    }

    // TODO: Implement hashCode() to maintain the contract with equals()
    
    @Override
    public String toString() {
        return "Product[" + sku + "]";
    }

    public static void main(String[] args) {
        Set<Product> catalog = new HashSet<>();
        
        Product p1 = new Product("JAVA-101", "Intro to Java Book");
        Product p2 = new Product("JAVA-101", "Intro to Java Book");
        
        catalog.add(p1);
        catalog.add(p2);
        
        System.out.println("Catalog size: " + catalog.size()); 
        // Expected output: Catalog size: 1
        // Current output: Catalog size: 2
    }
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.