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:
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:
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:
@Overrideasks the compiler to check that the method really replaces the method fromObject.- The header
public boolean equals(Object o)repeats the header fromObjectexactly: the parameter is of typeObject, notPoint. o instanceof Point pchecks that the other object is a point and at once names itpof typePoint. Fornulland for an object of another class, the check givesfalse.x == p.x && y == p.ycompares the fields. Thanks to the short-circuit evaluation of&&, this part runs only ifois 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:
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")); // trueObjects.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:
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:
error: method does not override or implement a method from a supertypeThe 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,
equalsfromObjectcompares references, like==, so two points(1, 2)are not equal. - Your own
equalsis written with the headerpublic boolean equals(Object o)and@Override;o instanceof Point pfilters outnulland objects of other classes. - Primitive fields are compared with
==, and object fields withObjects.equals, which handlesnullsafely. - Whenever you override
equals, you also overridehashCode.