Java Object Class
Introduction
java.lang.Object is the root of Java's class hierarchy. Every class directly or indirectly extends it, even when no superclass is written.
public class Employee { }
// Conceptually equivalent to:
public class Employee extends Object { }
That inheritance gives every object common operations for text representation, logical comparison, hashing, runtime type information, copying, and monitor-based thread coordination.
An Object reference can point to any class instance. The declared reference controls which methods are available at compile time, while overridden methods still use the object's runtime implementation.
Object value = "Java"; System.out.println(value); // String's toString() System.out.println(value.getClass()); // class java.lang.String
Methods in Object
| Method | Purpose |
|---|---|
getClass() | Returns runtime class information. |
toString() | Returns a textual representation. |
equals(Object) | Tests logical equality when overridden. |
hashCode() | Returns a value used by hash-based collections. |
clone() | Creates a shallow field-by-field copy when supported. |
wait(), notify(), notifyAll() | Coordinate threads through an object's monitor. |
finalize() | Deprecated cleanup hook; do not use it. |
Important Method Signatures
public final Class<?> getClass();
public int hashCode();
public boolean equals(Object object);
protected Object clone() throws CloneNotSupportedException;
public String toString();
public final void wait() throws InterruptedException;
public final void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos)
throws InterruptedException;
public final void notify();
public final void notifyAll();
@Deprecated
protected void finalize() throws Throwable;
getClass(), wait(), notify(), and notifyAll() are final. clone() and finalize() are protected, so ordinary application code cannot call them through an arbitrary reference without a class-level decision.
getClass()
getClass() is final and returns the runtime type of an object. It is useful for reflection, diagnostics, and strict equality checks.
Object value = "Java"; System.out.println(value.getClass().getName()); System.out.println(value.getClass().getSimpleName());
Possible output is java.lang.String and String. The result describes the runtime object, not merely the declared reference type.
toString()
The default form is usually a class name followed by @ and a hexadecimal hash-related value. Override it to make logs and debugging useful, but never expose passwords, tokens, or other sensitive data.
final class Employee {
private final long id;
private final String name;
Employee(long id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Employee{id=" + id + ", name='" + name + "'}";
}
}
equals() and ==
For object references, == compares identity: whether two references point to the same object. The default Object.equals() behaves similarly, but classes such as String override it to compare logical content.
String first = new String("Java");
String second = new String("Java");
System.out.println(first == second); // false
System.out.println(first.equals(second)); // true
Override equals() when separate instances should represent the same value according to stable business or value-object fields.
The equals() Contract
- Reflexive:
x.equals(x)is true. - Symmetric:
x.equals(y)andy.equals(x)agree. - Transitive: if x equals y and y equals z, x equals z.
- Consistent: repeated calls are stable while relevant state is unchanged.
- Non-null:
x.equals(null)is false.
Use a same-reference check, a null/type check, and null-safe field comparisons:
import java.util.Objects;
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
Employee other = (Employee) object;
return Objects.equals(id, other.id)
&& Objects.equals(name, other.name);
}
An exact getClass() check avoids many inheritance symmetry problems. instanceof can be appropriate for designed type hierarchies, but subclasses must not introduce equality rules that break symmetry or transitivity.
hashCode()
Hash-based collections such as HashMap, HashSet, LinkedHashMap, and ConcurrentHashMap use a hash code to locate a candidate bucket, then use equals() to confirm a match.
The hashCode() Contract
- Equal objects must have equal hash codes.
- Unequal objects may share a hash code; this is a collision.
- The result should remain stable while fields used by the calculation remain unchanged.
@Override
public int hashCode() {
return Objects.hash(id, name);
}
Whenever equals() is overridden, override hashCode() using the same equality fields. Otherwise HashSet and HashMap can fail to find logically equal objects.
Complete Equality Example
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
final class Employee {
private final Long id;
private final String name;
private final String department;
Employee(Long id, String name, String department) {
this.id = id;
this.name = name;
this.department = department;
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
Employee other = (Employee) object;
return Objects.equals(id, other.id)
&& Objects.equals(name, other.name)
&& Objects.equals(department, other.department);
}
@Override
public int hashCode() {
return Objects.hash(id, name, department);
}
@Override
public String toString() {
return "Employee{id=" + id + ", name='" + name
+ "', department='" + department + "'}";
}
}
Employee first = new Employee(101L, "Anand", "Engineering");
Employee second = new Employee(101L, "Anand", "Engineering");
Set<Employee> employees = new HashSet<>();
employees.add(first);
employees.add(second);
System.out.println(first.equals(second)); // true
System.out.println(employees.size()); // 1
Mutable Fields and Hash Collections
Do not change fields used by equals() or hashCode() after inserting an object into a hash-based collection. A changed hash can point to a different bucket, making the object appear to disappear.
Set<Employee> employees = new HashSet<>();
employees.add(employee);
employee.setEmail("new@example.com"); // dangerous if email is hashed
employees.contains(employee); // may now be false
Prefer immutable identifiers, stable natural keys, and immutable value objects. Avoid mutable collections, timestamps, lazy relationships, and frequently changing status fields in equality.
Objects Utility Methods
java.util.Objects makes implementations safer and shorter:
Objects.equals(first, second); // null-safe comparison Objects.hash(id, name); // combines fields Objects.requireNonNull(name); // validates a required value
For arrays, use Arrays.equals(), Arrays.deepEquals(), Arrays.hashCode(), or Arrays.deepHashCode() rather than treating an array like an ordinary object.
clone()
Object.clone() performs a shallow field-by-field copy and is protected. A class usually needs to implement Cloneable and handle CloneNotSupportedException. References to nested mutable objects are still shared in a shallow copy.
final class Employee implements Cloneable {
@Override
public Employee clone() {
try {
return (Employee) super.clone();
} catch (CloneNotSupportedException exception) {
throw new AssertionError(exception);
}
}
}
Prefer a copy constructor, static factory, or builder for explicit copying. These approaches make deep-copy decisions visible and avoid the surprising parts of the legacy clone mechanism.
wait(), notify(), and notifyAll()
These final methods coordinate threads through an object's monitor. They must be called while holding that object's monitor, normally inside a synchronized block.
synchronized (lock) {
while (!conditionIsReady) {
lock.wait();
}
}
synchronized (lock) {
conditionIsReady = true;
lock.notifyAll();
}
wait()releases the monitor and waits.notify()wakes one waiting thread.notifyAll()wakes all waiting threads.
Use a loop around wait() because spurious wakeups can occur and another thread may consume the condition first. For new concurrent code, prefer higher-level tools such as BlockingQueue, CountDownLatch, or CompletableFuture when they express the problem better.
wait() versus sleep()
| wait() | Thread.sleep() |
|---|---|
| Defined in Object | Defined in Thread |
| Releases the monitor | Does not release held locks |
| Used for coordination | Pauses execution for a duration |
| Requires synchronized context | Does not require synchronization |
finalize()
finalize() is deprecated and unreliable. It may never run, has unpredictable timing, adds overhead, and can create security and resurrection problems. Never use it for resource cleanup.
Use try-with-resources and AutoCloseable instead:
try (var connection = dataSource.getConnection()) {
// use the connection
} // closed automatically
Testing the equals() Contract
assertEquals(first, first); // reflexive
assertEquals(first, second); // symmetric with reverse check
assertEquals(second, third); // transitive setup
assertEquals(first, third);
assertNotEquals(first, null); // non-null rule
assertEquals(first.hashCode(),
second.hashCode()); // equal objects hash equally
Test equality independently from string formatting. Also test hash-based collection behavior with duplicate logical values and with keys that must be looked up using a different but equal instance.
Best Practices
- Override
equals()andhashCode()together. - Use exactly the same stable fields in both methods.
- Prefer immutable value objects and stable business keys.
- Keep lazy relationships and large object graphs out of equality.
- Override
toString()without exposing secrets. - Prefer copy constructors over
clone(). - Use higher-level concurrency utilities instead of low-level monitor methods when practical.
- Never use
finalize()for cleanup.
Quick Summary
Object is the root class. getClass() returns runtime type information. toString() describes an object. equals() defines logical equality. hashCode() supports hash-based lookup. clone() makes a shallow copy by default. wait(), notify(), and notifyAll() coordinate monitor waiters. finalize() is deprecated and should not be used.
