Kestrel Exams kestrel logo KESTREL EXAMSPRACTICE TO PASS
Java10 min readCode verified

The Object class: toString and writing your own equals

Every class you write has a parent even when you never typed extends: the class Object. This guide covers the two methods you inherit from it and almost always replace, toString and equals, including the one-word mistake that makes a hand-written equals quietly stop working. Every listing was compiled and run on OpenJDK 21.

Practice this topic in the free app →All Java guides

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:

MethodWhat Object’s version doesWhat you usually want
toString()returns the class name, an @, and a number in hexadecimalreadable text describing the object
equals(Object obj)returns true only if both are the very same objecttrue 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:

  1. Same object? If this == obj, return true. Quick and always correct.
  2. Wrong kind of thing? If obj is not a Point (and null never is), return false.
  3. Cast. Now that you know it is a Point, cast it so you can reach its fields.
  4. Compare the fields that matter. Use == for primitives like int and equals for objects like String.
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);.

One sentence to carry into the exam

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 public from toString or equals. The compiler rejects an override with weaker access.
  • Writing equals(Point other). It overloads instead of overriding, so collections like ArrayList never call it.
  • Skipping the instanceof check. Then comparing to a different class throws ClassCastException at the cast, and equals(null) throws NullPointerException as soon as it reads a field.
  • Comparing String fields with == inside equals. Use name.equals(other.name).
  • Expecting == to use your equals. It never does. == on objects always means same object.
  • Leaving off @Override. It is the check that catches both the misspelled tostring and 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.

  1. A class Pet has no toString. What does System.out.println(new Pet()) print, and why should you not rely on the number in it?
  2. Write a toString for a class Book with fields title and pages so that a book prints as Dune (412 pages).
  3. Point has not overridden equals. With Point a = new Point(3, 4); Point b = new Point(3, 4);, what are a == b and a.equals(b)?
  4. Why must the parameter of equals be Object and not the class’s own type?
  5. Write equals for a class Student with an int id and a String name. Two students are equal when both fields match.
  6. Which method does Java call to work out the text for "Owner: " + p, where p is an object?

Solutions#

  1. Something like Pet@1b6d3586: the class name, @, then the object’s hash code in hexadecimal, which is Object’s default toString. 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.
  2. 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)
  3. Both are false. == compares references, and Object’s equals does the same thing, so two separate objects are never equal until Point overrides equals.
  4. 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 calls equals(Object), such as ArrayList.contains, then runs Object’s version and compares references.
  5. Follow the four steps: same object, type check, cast, compare fields, using == for the int and equals for the String:
    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
  6. p.toString(). Joining any object to a String with + calls its toString, 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 without extends.
  • ☐  I can override toString() with the exact heading, and I know println and + call it for me.
  • ☐  I can write equals( Object obj ) in four steps and explain why the parameter is Object.
  • ☐  I can predict == and equals results 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.

Practise equals and the Object class →

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.

Suggest a change

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.