The Object Class in Java
Every class inherits from Object, which is where toString, equals, hashCode and getClass come from.
-
Java Basics
- Introduction to Java
- Setting Up Java and Writing Your First Program
- Variables, Data Types and Literals in Java
- Type Casting and Type Conversion in Java
- Operators and Expressions in Java
- Input and Output in Java
- Comments, Keywords and Naming Conventions in Java
- Control Flow in Java: if, else and switch
- Loops in Java: for, while and do-while
- Methods
- Arrays and Strings
-
OOP
- Classes and Objects in Java
- Constructors in Java
- The this Keyword in Java
- The static Keyword in Java
- Encapsulation in Java
- Access Modifiers in Java
- Inheritance in Java
- Method Overriding and super in Java
- Polymorphism in Java
- Abstraction, Abstract Classes and Interfaces in Java
- Composition, Aggregation and Association in Java
- The Object Lifecycle in Java
- Core Java
- Exception Handling
-
Collections
- The Java Collections Framework
- List in Java: ArrayList, LinkedList, Vector and Stack
- Set in Java: HashSet, LinkedHashSet and TreeSet
- Map in Java: HashMap, LinkedHashMap and TreeMap
- How HashMap Works Internally in Java
- Queue and Deque in Java: ArrayDeque and PriorityQueue
- Iterators in Java
- Comparable and Comparator in Java
- Collections Utilities and Choosing the Right Collection
- Generics
- Java 8+
- Stream API
- Date and Time
- File and I/O
-
Multithreading
- Threads in Java: Processes, Runnable and Thread
- Thread Lifecycle in Java
- Synchronization in Java: synchronized and volatile
- Locks and Atomic Classes in Java
- Race Conditions and Deadlocks in Java
- The Executor Framework and Thread Pools in Java
- Future and CompletableFuture in Java
- Concurrent Collections in Java
- The Java Memory Model
- JVM and Memory
- Advanced Java
- Networking
- JDBC
- Testing
The root of every hierarchy
java.lang.Object is the superclass of every class in Java. A class that declares no extends clause extends Object implicitly, and a class that extends something else still reaches Object further up the chain.
class Note { } // implicitly extends Object
Object anything = new Note(); // always valid
Object number = 42; // autoboxed to Integer, still an Object
Object array = new int[3]; // arrays are objects tooThis is why a List<Object> can hold anything, and why the collections framework could be written before generics existed.
The methods it provides
| Method | Purpose | Usually overridden |
|---|---|---|
toString() | A readable description | Yes |
equals(Object) | Logical equality | Yes, for value types |
hashCode() | A hash bucket for collections | Always with equals |
getClass() | The runtime class | No, it is final |
clone() | A field by field copy | Rarely, and reluctantly |
wait(), notify(), notifyAll() | Low level thread coordination | No, they are final |
toString
class Note {
private final long id;
private final String title;
Note(long id, String title) {
this.id = id;
this.title = title;
}
@Override
public String toString() {
return "Note[id=" + id + ", title=" + title + "]";
}
}Note note = new Note(7, "Java basics");
System.out.println(note); // Note[id=7, title=Java basics]
System.out.println("Saved " + note); // toString is called implicitlyThe inherited version prints the class name and an unsigned hexadecimal hash, which tells a reader almost nothing. Overriding toString is the cheapest improvement you can make to a class, because it pays off in every log line and every debugger session.
getClass
Object value = "text";
System.out.println(value.getClass().getName()); // java.lang.String
System.out.println(value.getClass().getSimpleName()); // String
System.out.println(value.getClass() == String.class); // truegetClass returns the runtime class, not the declared type. It is final, so no class can lie about what it is.
equals and hashCode, in brief
Note a = new Note(7, "Java basics");
Note b = new Note(7, "Java basics");
System.out.println(a.equals(b)); // false, unless equals is overriddenThe inherited equals compares references, so two objects are equal only if they are the same object. For value types that is rarely what you want. These two methods have a contract that must be respected together, covered in detail in the note on equals and hashCode.
clone and why it is avoided
class Settings implements Cloneable {
private final List<String> roles = new ArrayList<>();
@Override
protected Settings clone() throws CloneNotSupportedException {
Settings copy = (Settings) super.clone(); // shallow: roles is shared
return copy;
}
}Object.clone makes a shallow copy: every field is copied bit for bit, so referenced objects are shared rather than duplicated. The mechanism is awkward, requires the marker interface Cloneable, and throws a checked exception. A copy constructor or a static factory is clearer.
class Settings {
private final List<String> roles;
Settings(Settings other) { // a copy constructor
this.roles = new ArrayList<>(other.roles);
}
}The threading methods
wait, notify and notifyAll exist on Object because every object can act as a monitor lock. They must be called while holding that object monitor, and modern code should prefer the utilities in java.util.concurrent instead.
Common mistakes
- Printing an object and getting output such as
Note@1b6d3586, then wondering what went wrong. Nothing did;toStringwas simply not overridden. - Overriding
equalsand forgettinghashCode. - Writing
public boolean equals(Note other), which overloads rather than overrides. The parameter must beObject. - Using
cloneand assuming the copy is independent. - Comparing types with
getClass().getName().equals("...")instead ofinstanceof.
Best practices
- Override
toStringon every class that will appear in a log or an error message. - Override
equalsandhashCodetogether, or use a record and let the compiler do it. - Prefer a copy constructor over
clone. - Keep sensitive values such as passwords out of
toString.
Practice
- Print an object without overriding
toString, then add one and compare the output. - Why is
getClass()declaredfinal? - Explain the defect in
public boolean equals(Note other)and how@Overridewould have caught it. - Write a copy constructor for a class holding a mutable list, and show that changing the copy leaves the original alone.
- What does
new int[3].getClass().getSimpleName()return?
Conclusion
Everything in Java is an Object, which is why every reference offers toString, equals, hashCode and getClass. Override the first three thoughtfully, and prefer copy constructors to clone.