Skip to content
Theory · Wrappers and equality · lesson 7 of 8

equals in your own class

For strings and wrappers, equals compares the contents: the characters of a string, the number inside an Integer. But what does it do with objects of your own class? Let's take the familiar Point and compare it with another point with the same coordinates:

JAVA
public class Main {
    public static void main(String[] args) {
        Point a = new Point(1, 2);
        System.out.println(a.equals(new Point(1, 2))); // false
    }
}

class Point {
    int x;
    int y;
    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

The point a is (1, 2), and it is compared with a new point (1, 2). The coordinates match, yet equals answers false.

Back to the envelopes. For Integer, the method equals opens both envelopes and compares the coins. But the equals of your class does not know what is inside. It looks only at the envelopes themselves: is this one envelope or two? Here the picture is off: a Point envelope holds two coins at once, x and y, and what "equal" means for two points is up to the class itself.

equals from Object

Every object has the method equals. The class Point got it from Object, the root of all classes, as you saw in the inheritance theme. In Object this method returns this == obj, that is, it compares references.

So until a class overrides equals, a.equals(b) means the same as a == b for its objects. String and Integer override equals and compare contents. Your class can do that too.

What equals does when it is not overridden is a frequent interview question. The answer: it compares references, like ==.

Your own equals

Let's teach Point to compare coordinates. For that you add a method equals to the class, which replaces the method from Object:

JAVA
class Point {
    int x;
    int y;
    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    @Override
    public boolean equals(Object o) {
        return o instanceof Point p && x == p.x && y == p.y;
    }
}

Let's take the new method apart:

  1. @Override asks the compiler to check that the method really replaces the method from Object.
  2. The header public boolean equals(Object o) repeats the header from Object exactly: the parameter is of type Object, not Point.
  3. o instanceof Point p checks that the other object is a point and at once names it p of type Point. For null and for an object of another class, the check gives false.
  4. x == p.x && y == p.y compares the fields. Thanks to the short-circuit evaluation of &&, this part runs only if o is a point.

With this class the first program prints true, and a comparison with new Point(2, 1) gives false. A record writes such an equals by itself, over all its components, while an ordinary class has to be taught by hand.

Plain javac compiles this class silently, but javac -Xlint:all warns: Class Point overrides equals, but neither it nor any superclass overrides hashCode method. equals has a partner, the method hashCode, and in working code the two are only ever overridden together. Our Point does without it for now: why hashCode is needed, we will see in the collections theme.

Object fields and Objects.equals

In Point both fields are numbers, and == compares them. A field of a reference type, such as String name, cannot be compared that way: == would compare references. And the call name.equals(…) throws a NullPointerException if name holds null. For object fields there is the method Objects.equals:

JAVA
String a = null;
String b = null;
System.out.println(Objects.equals(a, b));           // true
System.out.println(Objects.equals(a, "Anna"));      // false
System.out.println(Objects.equals("Anna", "Anna")); // true

Objects.equals(a, b) works like this:

  • both arguments are null: true;
  • only one of them is null: false;
  • otherwise it calls a.equals(b).

The class Objects lives in the package java.util, so the file needs the line import java.util.Objects; at the top. Inside your own equals, a primitive field is compared with ==, and an object field with Objects.equals. For example, if a class User has a field String name, and the other object got the name u in the check o instanceof User u, that is Objects.equals(name, u.name).

Common mistake

The header of equals names your own class, and @Override is missing:

JAVA
class Point {
    int x;
    int y;
    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public boolean equals(Point p) { // ✗
        return x == p.x && y == p.y;
    }
}

This class compiles, and a.equals(b) for two variables of type Point even gives true. But this does not replace the method from Object; it overloads it: the class now has two equals methods, one with an Object parameter and one with a Point parameter. Code that sees the point only as an Object calls the old method, so Objects.equals(a, b) gives false. With @Override, the compiler would have caught the mistake at once:

TEXT
error: method does not override or implement a method from a supertype

The parameter of equals is always of type Object, and @Override above the method guards against a mistake in the header.

Key points

  • Without an override, equals from Object compares references, like ==, so two points (1, 2) are not equal.
  • Your own equals is written with the header public boolean equals(Object o) and @Override; o instanceof Point p filters out null and objects of other classes.
  • Primitive fields are compared with ==, and object fields with Objects.equals, which handles null safely.
  • Whenever you override equals, you also override hashCode.