Java Object Class
Updated 10 min read Aimerscrew
The Java Object class sits at the very top of every class hierarchy. Even when you write a class without extends, Java quietly makes it a subclass of java.lang.Object. That's why every object you create already has a handful of methods, such as toString(), equals() and hashCode(). In this lesson, you'll learn what those methods do by default, why the defaults are rarely what you want, and how to override them correctly.
🌍 Real-life example
In a library, two copies of the same novel are two separate physical books, but the catalog treats them as the same title because they share an ISBN number. Java needs the same kind of rule. By default it only knows "is this the exact same object?". By overriding equals(), you teach it "same ISBN means same book".
What is the Object class in Java?
Object is the superclass of all classes in Java. Writing class Book { } is exactly the same as writing class Book extends Object { }. If a class extends another class, the chain still ends at Object: a Car extends Vehicle, and Vehicle extends Object.
Because of this, a variable of type Object can hold any object, and every object inherits these useful methods:
| Method | What it gives you | Default behavior |
|---|---|---|
toString() | A text version of the object | Class name, @, and a hex number |
equals(Object o) | Are two objects "equal"? | Same as ==: same object only |
hashCode() | A number used by hash-based collections | Usually different for every object |
getClass() | The object's real class at run time | Can't be overridden |
How do you override toString() in Java?
If you print an object whose class doesn't override toString(), Java prints something like Book@1b6d3586: the class name, an @ sign and a number in hexadecimal that differs from run to run. It's not useful to a human.
Override toString() to return a readable description. Java calls it automatically whenever an object is printed or joined to a string with +:
public class Library {
public static void main(String[] args) {
Book book = new Book("The Silent Harbor", "R. Okafor", 2019);
System.out.println(book); // println calls toString()
System.out.println("On loan: " + book); // so does + with a String
String text = book.toString(); // or call it yourself
System.out.println(text.length() + " characters");
}
}
class Book {
private String title;
private String author;
private int year;
Book(String title, String author, int year) {
this.title = title;
this.author = author;
this.year = year;
}
@Override
public String toString() {
return "\"" + title + "\" by " + author + " (" + year + ")";
}
}
"The Silent Harbor" by R. Okafor (2019)
On loan: "The Silent Harbor" by R. Okafor (2019)
39 characters
Note that toString() must be public and return a String, because that's how Object declares it. It should return the text, not print it.
💡 Tip
Override toString() in almost every class you write. It makes debugging much easier: one println shows you what's inside an object.
== vs equals(): a quick recap
You learned in Java string comparison that == checks whether two references point to the same object, while equals() is meant to check whether two objects have the same content. String overrides equals() to compare characters. Your own classes don't, until you write it:
public class EqualityCheck {
public static void main(String[] args) {
Book a = new Book("978-1-4028-9462-6", "The Silent Harbor");
Book b = new Book("978-1-4028-9462-6", "The Silent Harbor");
Book c = a;
System.out.println("a == b: " + (a == b));
System.out.println("a == c: " + (a == c));
System.out.println("a.equals(b): " + a.equals(b));
}
}
class Book {
private String isbn;
private String title;
Book(String isbn, String title) {
this.isbn = isbn;
this.title = title;
}
}
a == b: false
a == c: true
a.equals(b): false
a and b hold identical data, but they are two separate objects. The equals() inherited from Object just does ==, so it says false too.
How to override equals() correctly
The method must be declared exactly as public boolean equals(Object o). The parameter type is Object, not Book. A typical implementation has three steps:
import java.util.Objects;
public class BookEquals {
public static void main(String[] args) {
Book a = new Book("978-1-4028-9462-6", "The Silent Harbor");
Book b = new Book("978-1-4028-9462-6", "The Silent Harbor (2nd printing)");
Book other = new Book("978-0-3164-8441-2", "Northern Lights Cafe");
System.out.println("a == b: " + (a == b));
System.out.println("a.equals(b): " + a.equals(b));
System.out.println("a.equals(other): " + a.equals(other));
System.out.println("a.equals(null): " + a.equals(null));
System.out.println("Same hash code? " + (a.hashCode() == b.hashCode()));
}
}
class Book {
private final String isbn;
private final String title;
Book(String isbn, String title) {
this.isbn = isbn;
this.title = title;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true; // same object
}
if (!(o instanceof Book other)) {
return false; // null or not a Book
}
return isbn.equals(other.isbn); // same ISBN = same book
}
@Override
public int hashCode() {
return Objects.hash(isbn); // uses the same field as equals
}
@Override
public String toString() {
return title + " [" + isbn + "]";
}
}
a == b: false
a.equals(b): true
a.equals(other): false
a.equals(null): false
Same hash code? true
- Same object? Then they're obviously equal.
- Right type?
o instanceof Book other(the pattern matching you saw earlier) isfalsefornulland for non-books, so both cases returnfalsesafely. - Compare the fields that define identity. Here, only the ISBN matters; a different printing title is still the same book.
A correct equals() follows common-sense rules: an object equals itself, a.equals(b) gives the same answer as b.equals(a), and nothing equals null.
The equals and hashCode contract
A hash code is an int computed from an object's data. Collections like HashSet and HashMap use it to decide which "bucket" to store an object in, so they can find it again quickly. Java has a firm rule, known as the equals/hashCode contract:
⚠️ The contract
If two objects are equal according to equals(), they must return the same hashCode(). So whenever you override equals(), override hashCode() too, using the same fields. (The reverse is not required: two different objects may share a hash code.)
The easiest way to write hashCode() is Objects.hash(field1, field2, ...) from java.util.Objects, passing exactly the fields you compare in equals().
hashCode() picks the bucket, then equals() checks inside it.Seeing the contract in action with HashSet and HashMap
Here the Book class overrides both methods. A HashSet refuses the duplicate, and a HashMap finds a value using a brand-new but equal key. (You'll learn these collections properly in Java HashSet and Java HashMap; for now, a set holds unique items and a map links keys to values.)
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class ShelfFinder {
public static void main(String[] args) {
Set<Book> borrowed = new HashSet<>();
borrowed.add(new Book("978-1-4028-9462-6", "The Silent Harbor"));
borrowed.add(new Book("978-1-4028-9462-6", "The Silent Harbor")); // duplicate
Map<Book, String> shelves = new HashMap<>();
shelves.put(new Book("978-0-3164-8441-2", "Northern Lights Cafe"), "Shelf B3");
// Brand-new objects, but equal to the ones stored
Book lookup1 = new Book("978-1-4028-9462-6", "The Silent Harbor");
Book lookup2 = new Book("978-0-3164-8441-2", "Northern Lights Cafe");
System.out.println("Books in set: " + borrowed.size());
System.out.println("Is it borrowed? " + borrowed.contains(lookup1));
System.out.println("Where is it? " + shelves.get(lookup2));
}
}
class Book {
private final String isbn;
private final String title;
Book(String isbn, String title) {
this.isbn = isbn;
this.title = title;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Book other)) {
return false;
}
return isbn.equals(other.isbn);
}
@Override
public int hashCode() {
return Objects.hash(isbn);
}
@Override
public String toString() {
return title;
}
}
Books in set: 1
Is it borrowed? true
Where is it? Shelf B3
Now delete the hashCode() method and run it again. You'll almost certainly see Books in set: 2, false and null. Each new object gets its own default hash code, so Java looks in the wrong bucket and never even calls your equals(). That's the bug the contract prevents.
getClass(): finding an object's real class
getClass() returns the class of the actual object, no matter what type the variable has. getSimpleName() gives you the class name as a short string:
public class WhatAmI {
public static void main(String[] args) {
Object[] things = { new Dog(), new Cat(), "hello", new int[3] };
for (Object thing : things) {
System.out.println(thing.getClass().getSimpleName());
}
Animal pet = new Dog();
System.out.println("Variable type: Animal, object type: " + pet.getClass().getSimpleName());
System.out.println("Same class as another Dog? " + (pet.getClass() == new Dog().getClass()));
}
}
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
Dog
Cat
String
int[]
Variable type: Animal, object type: Dog
Same class as another Dog? true
An Object[] array can hold dogs, strings and even other arrays, because every one of them is an Object. getClass() is final, so you can't override it.
Common mistakes with Object methods
1. Writing equals(Book o) instead of equals(Object o)
A method public boolean equals(Book o) doesn't override anything; it's an overload. Collections still call the old equals(Object). Add @Override and the compiler will catch it.
2. Overriding equals() but not hashCode()
Your objects look equal when you call equals() yourself, but a HashSet stores duplicates and a HashMap returns null. Always override both, with the same fields. Compiling with javac -Xlint:all even warns you: Class Book overrides equals, but neither it nor any superclass overrides hashCode method.
3. Using fields that can change
If a field used in hashCode() changes after the object is put in a HashSet, the set looks in the wrong bucket and "loses" it. Base equality on fields that don't change, and mark them final where you can.
4. Printing inside toString()
toString() should return a string, not call System.out.println. Callers decide where the text goes.
Practice exercise
🏋️ Try it yourself
An airline stores booked seats in a HashSet. Create a Seat class with a row number and a seat letter (like 12 and 'C'). Override toString() to return Seat 12C, and override equals() and hashCode() so two seats with the same row and letter are equal. Book 12C and 3A, then check whether a new Seat(12, 'C') and Seat(14, 'F') are already booked.
Show solution
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class FlightSeats {
public static void main(String[] args) {
Set<Seat> booked = new HashSet<>();
booked.add(new Seat(12, 'C'));
booked.add(new Seat(3, 'A'));
Seat request = new Seat(12, 'C');
System.out.println("Requested: " + request);
System.out.println("Already booked? " + booked.contains(request));
System.out.println("Is 14F booked? " + booked.contains(new Seat(14, 'F')));
}
}
class Seat {
private final int row;
private final char letter;
Seat(int row, char letter) {
this.row = row;
this.letter = letter;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Seat other)) {
return false;
}
return row == other.row && letter == other.letter;
}
@Override
public int hashCode() {
return Objects.hash(row, letter);
}
@Override
public String toString() {
return "Seat " + row + letter;
}
}
Requested: Seat 12C
Already booked? true
Is 14F booked? false
What's next?
Seats, sizes, days of the week and order statuses often come from a fixed list of possible values. Java has a special kind of class just for that. Next, learn about Java enums.
Key takeaways
- Every Java class extends java.lang.Object, directly or through its parents, so every object has toString, equals, hashCode and getClass.
- The default toString prints something like ClassName@1b6d3586; override it to return a readable description.
- The default equals is the same as ==; override it as public boolean equals(Object o) to compare by content.
- The contract: objects that are equal must have the same hashCode, so always override equals and hashCode together, using the same fields.
- Objects.hash(...) is the easiest way to write a correct hashCode.
- getClass() returns the real class of the object at run time, whatever the variable type is.
Frequently asked questions
What is the Object class in Java?
Object, in the java.lang package, is the root superclass of every Java class. Any class that does not extend another class extends Object automatically, so all objects share its methods such as toString, equals and hashCode.
Why should I override toString in Java?
The default toString returns the class name and a hexadecimal hash, which is not readable. Overriding it lets println and string concatenation show the object's actual data, which makes output and debugging much clearer.
Why must hashCode be overridden when equals is overridden?
Hash-based collections like HashSet and HashMap first use hashCode to find a bucket and only then call equals. If equal objects have different hash codes, the collection looks in the wrong bucket and treats them as different.
What is the difference between == and equals in Java?
== checks whether two references point to the same object in memory. equals checks whether two objects are logically equal, based on the rules the class defines; by default it behaves like ==.
Can two different objects have the same hashCode?
Yes. That is allowed and is called a collision. The contract only requires that equal objects have equal hash codes, not that unequal objects have different ones.