toString() method
The toString() method is used to get a string representation of an object. It returns a string that "textually represents" the object. The default implementation of this method returns a string consisting of the object's class name, an '@' sign, & the unsigned hexadecimal representation of the object's hash code.
Example :
Java
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
public static void main(String[] args) {
Person person = new Person("Rahul", 25);
System.out.println(person.toString());
}
}

You can also try this code with Online Java Compiler
Run Code
Output:
Person [name=Rahul, age=25]
In this example, we have overridden the toString() method in the Person class to provide a custom string representation of the Person object. When we call the toString() method on the Person object, it returns the string representation as defined in the overridden method.
hashCode() method
The hashCode() method is used to generate a unique integer value for an object, which is known as the hash code. The hash code is used by hash-based data structures like HashMap, HashSet, & Hashtable to store & retrieve objects efficiently.
The main purpose of the hashCode() method is to provide a way to quickly compare objects for equality. When you put an object into a hash-based collection or use it as a key in a hash-based map, the hashCode() method is called to calculate the hash code of the object. The hash code is then used to determine the bucket or slot in the hash table where the object should be stored.
Here are a few important points about the hashCode() method:
- Consistency: If two objects are equal according to the equals() method, then their hash codes must also be equal. This means that if you override the equals() method, you should also override the hashCode() method to ensure that equal objects have the same hash code.
- Uniqueness: Ideally, the hashCode() method should generate a unique hash code for each unique object. However, in practice, it is possible for two unequal objects to have the same hash code, which is known as a hash collision. Hash-based collections handle collisions by storing objects with the same hash code in the same bucket or linked list.
- Speed: The hashCode() method should be fast to compute & should not be too complex. A good hash code function should distribute objects evenly across the hash table to minimize collisions & ensure efficient retrieval.
- Immutability: If an object is used as a key in a hash-based collection, its hash code should not change over time. This means that the object should be immutable or its state should not be modified after it is inserted into the collection.
By default, the hashCode() method inherited from the Object class returns a unique integer value for each object. However, you can override the hashCode() method in your own classes to provide a custom hash code calculation based on the object's attributes.
Example :
Java
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
public static void main(String[] args) {
Person person1 = new Person("Rinki", 30);
Person person2 = new Person("Rinki", 30);
System.out.println(person1.hashCode());
System.out.println(person2.hashCode());
}
}

You can also try this code with Online Java Compiler
Run Code
Output :
-1609845986
-1609845986
In this example, we have overridden the hashCode() method in the Person class to provide a custom hash code calculation based on the name & age attributes of the Person object. When we call the hashCode() method on two Person objects with the same attribute values, it returns the same hash code value.
equals(Object obj) method
The equals(Object obj) method is used to compare two objects for equality. It returns true if the objects are equal & false otherwise. The default implementation of the equals() method in the Object class compares the object references, which means it returns true only if the two references point to the same object in memory.
However, in most cases, you'll want to compare objects based on their contents rather than their references. To do this, you need to override the equals() method in your class.
Example :
Java
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null || getClass() != obj.getClass())
return false;
Person other = (Person) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
public static void main(String[] args) {
Person person1 = new Person("Harsh", 25);
Person person2 = new Person("Harsh", 25);
Person person3 = new Person("Sanjana", 30);
System.out.println(person1.equals(person2));
System.out.println(person1.equals(person3));
}
}

You can also try this code with Online Java Compiler
Run Code
Output:
true
false
In this example, we have overridden the equals() method in the Person class to compare two Person objects based on their name & age attributes. The equals() method first checks if the two objects are the same instance (this == obj). If not, it checks if the object is null or if the classes of the two objects are different. If the classes are the same, it casts the obj to a Person & compares the name & age attributes. If all the attributes are equal, the method returns true; otherwise, it returns false.
When we call the equals() method on person1 & person2, which have the same attribute values, it returns true. When we call the equals() method on person1 & person3, which have different attribute values, it returns false.
finalize() method
The finalize() method is called by the garbage collector before an object is destroyed. It is used to perform cleanup activities, such as releasing resources held by the object. The finalize() method is defined in the Object class as:
protected void finalize() throws Throwable {
// cleanup code
}
The finalize() method is called automatically by the garbage collector when it determines that there are no more references to the object. However, it is not guaranteed that the finalize() method will be called immediately after an object becomes eligible for garbage collection. The garbage collector may delay the finalization of an object until a later time, or it may not call the finalize() method at all if the JVM exits before the object is destroyed.
Example :
Java
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
@Override
protected void finalize() throws Throwable {
System.out.println("Finalizing " + name);
// cleanup code
super.finalize();
}
public static void main(String[] args) {
Person person1 = new Person("Sinki");
Person person2 = new Person("Ravi");
person1 = null;
person2 = null;
System.gc();
}
}

You can also try this code with Online Java Compiler
Run Code
Output:
Finalizing Sinki
Finalizing Ravi
In this example, we have overridden the finalize() method in the Person class to print a message when the object is being finalized. In the main() method, we create two Person objects & then set their references to null, making them eligible for garbage collection. We then call the System.gc() method to suggest to the JVM that it should perform garbage collection.
When the garbage collector runs, it calls the finalize() method on the Person objects before destroying them. The output shows that the finalize() method is called for both objects.
It's important to note that the finalize() method is not guaranteed to be called & should not be relied upon for critical cleanup tasks. It is better to use explicit cleanup methods, such as close() for I/O streams, to ensure that resources are properly released.
getClass() method
The getClass() method is used to get the runtime class of an object. It returns a Class object that represents the runtime class of the object. The Class object contains information about the object's class, such as its name, superclass, interfaces, & methods.
Example :
Java
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public static void main(String[] args) {
Person person = new Person("Mehak");
Class<?> cls = person.getClass();
System.out.println("Class name: " + cls.getName());
System.out.println("Is interface: " + cls.isInterface());
System.out.println("Is array: " + cls.isArray());
System.out.println("Is primitive: " + cls.isPrimitive());
}
}

You can also try this code with Online Java Compiler
Run Code
Output:
Class name: Person
Is interface: false
Is array: false
Is primitive: false
In this example, we create a Person object & then use the getClass() method to get its runtime class. We store the Class object in a variable named cls. We then use various methods of the Class object to get information about the class, such as its name, whether it is an interface, an array, or a primitive type.
The getClass() method is useful in situations where you need to dynamically determine the type of an object at runtime. It can be used for tasks such as reflection, serialization, & type checking.
clone() method
The clone() method is used to create a copy of an object. It creates a new object with the same state as the original object. The clone() method is defined in the Object class as:
javaCopyprotected native Object clone() throws CloneNotSupportedException;
To use the clone() method, a class must implement the Cloneable interface. The Cloneable interface is a marker interface that indicates that the class allows cloning. If a class does not implement the Cloneable interface & tries to call the clone() method, a CloneNotSupportedException will be displayed.
Example :
Java
public class Person implements Cloneable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) {
Person person1 = new Person("Rahul", 25);
try {
Person person2 = (Person) person1.clone();
System.out.println(person1 == person2);
System.out.println(person1.equals(person2));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}

You can also try this code with Online Java Compiler
Run Code
Output:
false
true
In this example, we have a Person class that implements the Cloneable interface. We override the clone() method & simply call super.clone() to create a shallow copy of the object.
In the main() method, we create a Person object named person1 & then use the clone() method to create a copy of it named person2. We then compare the two objects using the == operator & the equals() method.
The output shows that person1 & person2 are not the same object (== returns false), but they are equal (equals() returns true). This means that person2 is a separate object with the same state as person1.
It's important to note that the clone() method creates a shallow copy of the object, which means that any reference variables in the object will still point to the same objects in the original & cloned objects. If you need a deep copy, where the referenced objects are also cloned, you'll need to implement your own deep cloning logic.
wait(), notify() & notifyAll() methods:
The wait(), notify(), & notifyAll() methods are used for thread synchronization in Java. They allow threads to communicate & coordinate their actions.
- wait(): The wait() method causes the current thread to wait until another thread calls the notify() or notifyAll() method for this object. The wait() method releases the lock on the object & allows other threads to acquire the lock & execute.
- notify(): The notify() method wakes up a single thread that is waiting on this object's monitor. If multiple threads are waiting, one of them is chosen arbitrarily to be awakened. The chosen thread will not run immediately, but will compete for the lock on the object with other threads.
- notifyAll(): The notifyAll() method wakes up all threads that are waiting on this object's monitor. All the awakened threads will compete for the lock on the object.
Example :
Java
public class WaitNotifyExample {
private static final Object lock = new Object();
public static void main(String[] args) {
new Thread(new Waiter(), "Waiter Thread").start();
new Thread(new Notifier(), "Notifier Thread").start();
}
static class Waiter implements Runnable {
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " is waiting.");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " is notified.");
}
}
}
static class Notifier implements Runnable {
@Override
public void run() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " is notifying.");
lock.notify();
}
}
}
}

You can also try this code with Online Java Compiler
Run Code
Output:
Waiter Thread is waiting.
Notifier Thread is notifying.
Waiter Thread is notified.
In this example, we have two threads: Waiter Thread & Notifier Thread. The Waiter Thread acquires the lock on the lock object & calls the wait() method, which causes it to release the lock & wait until it is notified. The Notifier Thread acquires the lock on the lock object & calls the notify() method, which wakes up the Waiter Thread.
The output shows that the Waiter Thread is waiting, then the Notifier Thread notifies it, & finally, the Waiter Thread is notified & continues execution.
The wait(), notify(), & notifyAll() methods are essential for implementing thread coordination & avoiding race conditions in multi-threaded programs.
Frequently Asked Questions
Can we override the Object class methods in Java?
Yes, we can override the methods of the Object class in our own classes to provide custom implementations specific to our needs.
Is it necessary to override the equals() method if we override the hashCode() method?
Yes, if we override the hashCode() method, it is recommended to also override the equals() method to maintain the contract between them.
What happens if a class does not implement the Cloneable interface & tries to call the clone() method?
If a class does not implement the Cloneable interface & tries to call the clone() method, a CloneNotSupportedException will be thrown.
Conclusion
In this article, we have learned about the Object class in Java & its various methods. We explored the toString(), hashCode(), equals(), finalize(), getClass(), clone(), wait(), notify(), & notifyAll() methods & saw how they can be used in Java programs. These methods are crucial for working with objects effectively in Java. By overriding these methods in our own classes, we can provide custom behavior & ensure proper object comparison, hashing, & synchronization.
You can also practice coding questions commonly asked in interviews on Coding Ninjas Code360.
Also, check out some of the Guided Paths on topics such as Data Structure and Algorithms, Competitive Programming, Operating Systems, Computer Networks, DBMS, System Design, etc., as well as some Contests, Test Series, and Interview Experiences curated by top Industry Experts.