The top of every family tree#
Trace any family tree back far enough and you reach one shared ancestor. In Java that ancestor is a class called Object. A class with no extends is treated as if you had written extends Object. A class that extends something else still reaches Object further up.
So every object in every Java program inherits a small set of methods from Object. Two of them matter for this course:
| Method | What Object’s version does | What you usually want |
|---|---|---|
toString() | returns the class name, an @, and a number in hexadecimal | readable text describing the object |
equals(Object obj) | returns true only if both are the very same object | true if the two objects hold the same values |
Both defaults are honest guesses by a class that knows nothing about your fields. Overriding them is how you teach Java what your class means.
What toString gives you by default#
class Pet {
private String name;
public Pet(String name) {
this.name = name;
}
}
public class Default {
public static void main(String[] args) {
Pet p = new Pet("Rex");
System.out.println(p);
System.out.println(p.toString());
System.out.println("Pet: " + p);
}
}
Pet@15db9742
Pet@15db9742
Pet: Pet@15db9742
The text after @ is the object’s hash code shown in hexadecimal: a number Java works out from the object, not a memory address. You may well see a different number on your computer, and nothing guarantees it stays the same from one run to the next, so never rely on it. It tells you nothing about the pet.
Notice the first two lines are identical. println calls toString() for you whenever you hand it an object. So does string joining with +: "Pet: " + p quietly becomes "Pet: " + p.toString(). That is why one override fixes every place the object is printed.
Overriding toString#
Write a method with exactly this heading: public String toString(). It returns the text. It does not print it.
import java.util.Arrays;
class Pet {
private String name;
private int age;
public Pet(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " (age " + age + ")";
}
}
public class ToString {
public static void main(String[] args) {
Pet p = new Pet("Rex", 3);
System.out.println(p);
System.out.println("Today we walked " + p + ".");
Pet[] all = { p, new Pet("Mia", 5) };
System.out.println(Arrays.toString(all));
}
}
Rex (age 3)
Today we walked Rex (age 3).
[Rex (age 3), Mia (age 5)]
One method, and the object now prints sensibly on its own, inside a sentence, and inside an array printed with Arrays.toString.
The heading has to match Object’s exactly. Leave off public and the compiler refuses, because an override is not allowed to be more private than the method it replaces:
class Pet {
private String name = "Rex";
@Override
String toString() {
return name;
}
}
public class Weaker {
public static void main(String[] args) {
System.out.println(new Pet());
}
}
Weaker.java:5: error: toString() in Pet cannot override toString() in Object
String toString() {
^
attempting to assign weaker access privileges; was public
1 error
Two more slips are worth knowing. A public void toString() that prints instead of returning does not compile at all, because its return type clashes with Object’s. The sneakier one is public String tostring() with a lowercase s: it compiles happily as a brand-new method, and your objects keep printing Pet@.... That is what @Override is for.
Why two identical objects are not equal#
The strings guide showed two copies of the same book: same words, different books. Object’s equals asks only whether it is the same physical book. It is == wearing a method’s clothes.
class Point {
private int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
public class DefaultEq {
public static void main(String[] args) {
Point a = new Point(3, 4);
Point b = new Point(3, 4);
Point c = a;
System.out.println(a == b);
System.out.println(a.equals(b));
System.out.println(a == c);
System.out.println(a.equals(c));
}
}
false
false
true
true
a and b hold the same numbers, but they are two separate objects, so both tests say false. c is a second name for the object a already points at, so both tests say true. To make a.equals(b) true, Point must override equals and say which fields count.
Writing equals(Object obj), step by step#
The parameter type is Object, not Point. That is not optional: it is what makes your method an override of the one every class inherits. The body follows the same four steps every time:
- Same object? If
this == obj, return true. Quick and always correct. - Wrong kind of thing? If
objis not aPoint(andnullnever is), return false. - Cast. Now that you know it is a
Point, cast it so you can reach its fields. - Compare the fields that matter. Use
==for primitives likeintandequalsfor objects likeString.
class Point {
private int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Point)) {
return false;
}
Point other = (Point) obj;
return x == other.x && y == other.y;
}
}
public class Equals {
public static void main(String[] args) {
Point a = new Point(3, 4);
Point b = new Point(3, 4);
Point d = new Point(4, 3);
System.out.println(a == b);
System.out.println(a.equals(b));
System.out.println(a.equals(d));
System.out.println(a.equals(null));
System.out.println(a.equals("3, 4"));
}
}
false
true
false
false
false
The last line shows step 2 doing its job: comparing a point to a String returns false instead of crashing. The instanceof test is also what protects you from null, because null instanceof Point is always false.
The cast in step 3 is needed because, as far as the compiler knows, obj is just an Object, and an Object has no field called x. The polymorphism guide explains this rule: the variable’s type decides what you are allowed to call.
The equals(Point other) trap#
It is tempting to write public boolean equals(Point other). It looks tidier and it skips the cast. It also compiles. But a different parameter type means it is an overload, a new method beside Object’s, not an override of it. Anything in Java that calls equals(Object) never sees it.
import java.util.ArrayList;
class Point {
private int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public boolean equals(Point other) {
return x == other.x && y == other.y;
}
}
public class Trap {
public static void main(String[] args) {
Point a = new Point(3, 4);
Point b = new Point(3, 4);
System.out.println(a.equals(b));
ArrayList<Point> list = new ArrayList<>();
list.add(a);
System.out.println(list.contains(b));
}
}
true
false
Called directly with a Point variable, the overload runs and says true. But ArrayList.contains (covered in the ArrayList guide) calls equals(Object), which this class never replaced, so it falls back to Object’s same-object test and says false. Same two points, two different answers. Put @Override on your equals and the compiler rejects the wrong parameter type before this can happen.
One more rule worth knowing, even though it sits just past most first courses: when you override equals, also override hashCode so that equal objects give equal hash codes. HashMap and HashSet depend on it. The usual one-liner is return Objects.hash(x, y);.
Object’s toString prints the class name and a hash code, and its equals asks “same object?”; override public String toString() to return text, and public boolean equals(Object obj), with Object as the parameter, to compare fields.
Where these go wrong#
- Printing inside
toString. It must return a String. Let the caller print it. - Dropping
publicfromtoStringorequals. The compiler rejects an override with weaker access. - Writing
equals(Point other). It overloads instead of overriding, so collections likeArrayListnever call it. - Skipping the
instanceofcheck. Then comparing to a different class throwsClassCastExceptionat the cast, andequals(null)throwsNullPointerExceptionas soon as it reads a field. - Comparing String fields with
==insideequals. Usename.equals(other.name). - Expecting
==to use yourequals. It never does.==on objects always means same object. - Leaving off
@Override. It is the check that catches both the misspelledtostringand the wrong parameter type.
Practice#
Answer each one on paper before you look at the solutions. Every answer that involves running code was checked by compiling and running it.
- A class
Pethas notoString. What doesSystem.out.println(new Pet())print, and why should you not rely on the number in it? - Write a
toStringfor a classBookwith fieldstitleandpagesso that a book prints asDune (412 pages). Pointhas not overriddenequals. WithPoint a = new Point(3, 4); Point b = new Point(3, 4);, what area == banda.equals(b)?- Why must the parameter of
equalsbeObjectand not the class’s own type? - Write
equalsfor a classStudentwith anint idand aString name. Two students are equal when both fields match. - Which method does Java call to work out the text for
"Owner: " + p, wherepis an object?
Solutions#
- Something like
Pet@1b6d3586: the class name,@, then the object’s hash code in hexadecimal, which isObject’s defaulttoString. The number can differ between computers and Java versions, and Java does not promise it stays the same between runs, and it says nothing about the pet’s fields. - Return the text; do not print it:
class Book { private String title; private int pages; public Book(String title, int pages) { this.title = title; this.pages = pages; } @Override public String toString() { return title + " (" + pages + " pages)"; } } public class QBook { public static void main(String[] args) { System.out.println(new Book("Dune", 412)); } }Dune (412 pages) - Both are false.
==compares references, andObject’sequalsdoes the same thing, so two separate objects are never equal untilPointoverridesequals. - Because only
equals(Object obj)overrides the method every class inherits. With any other parameter type the method is an overload, a separate method. Code that callsequals(Object), such asArrayList.contains, then runsObject’s version and compares references. - Follow the four steps: same object, type check, cast, compare fields, using
==for theintandequalsfor theString:class Student { private int id; private String name; public Student(int id, String name) { this.id = id; this.name = name; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof Student)) { return false; } Student other = (Student) obj; return id == other.id && name.equals(other.name); } } public class QStudent { public static void main(String[] args) { Student s1 = new Student(7, "Ana"); Student s2 = new Student(7, "Ana"); Student s3 = new Student(7, "Ann"); System.out.println(s1.equals(s2)); System.out.println(s1.equals(s3)); } }true false p.toString(). Joining any object to a String with+calls itstoString, which is why overriding it once improves every place the object appears in text.
Test-readiness checklist#
These are the items from the course’s Exam 2 topic list that this guide covers. Tick each one only when you can do it without looking.
- ☐ I can explain that every class inherits from
Object, with or withoutextends. - ☐ I can override
toString()with the exact heading, and I knowprintlnand+call it for me. - ☐ I can write
equals( Object obj )in four steps and explain why the parameter isObject. - ☐ I can predict
==andequalsresults for two objects with the same field values, before and after overriding.
Test yourself in the free Kestrel Exams app
40 practice questions on the Object class, equals, == and hashCode, with every code answer checked by compiling and running it. Free, offline, no account.
Frequently asked questions#
What is the Object class in Java?
Object is the class at the top of Java's class hierarchy. Every class extends it, directly when no extends clause is written, or indirectly through its parent. So every object inherits Object's methods, including toString, equals and hashCode.
Why does printing my object show something like Pet@1b6d3586?
Because the class has not overridden toString, so Object's version runs. It returns the class name, an @ sign and the object's hash code in hexadecimal. Override public String toString() to return readable text instead.
What does the default equals method do?
Object's equals returns true only when both references point to the same object, exactly like ==. Two separate objects with identical field values are not equal until the class overrides equals.
Why is the parameter of equals Object and not my class?
Only equals(Object obj) overrides the inherited method. A method like equals(Point p) is an overload, a separate method, so code that calls equals(Object), including ArrayList.contains and indexOf, never uses it.
How do I write an equals method in Java?
Return true if this == obj. Return false if obj is not an instance of your class, which also handles null. Cast obj to your class. Then compare the fields that define equality, using == for primitives and equals for objects such as Strings.
Do I need to override hashCode when I override equals?
Yes, whenever objects may be stored in a HashMap or HashSet. Equal objects must return equal hash codes, or those collections will not find them. Objects.hash(field1, field2) is a simple way to write it.
Something here not clear? A topic you wish we covered? Tell us. We read every message, and a request is the fastest way to get a guide written — several of these exist because somebody asked.