The Object class in Java

You might have seen many articles describing the Object class in Java, but why does it exist? What use case does it satisfy? Let’s explore that in this article by going step by step and understanding what if we write code being unaware of the Object class and then what benefits does using the Object class have.

For those who don’t know, The Object class is the super most class in the Java Language. All of Java’s internal as well as all user-defined classes extend the Object class.

Let’s take an example where we are building a small feature in an ATS(Application Tracker System) for Job Applications in a company.

Let’s look at a simple Candidate class representing a candidate who has applied for a job.

Now let’s try to use this class to prepare an object and print the contents.

If you execute this code, following will be the output.

At line 2, the only familiar word is out class name Candidate , res everything after it looks like random string.

What does look unfamiliar is the memory address of our Candidate class object that we had prepared.

But in a real world use case why would a user want to see the memory address of an object? The user wouldn’t. The user would want to see his name, id and other relevant properties and not a random memory address. How can we give the user the necessary details?

The first thing which most of you would say is just define a method called getCandidateDetails and print all the relevant properties of a candidate. Yes, that is correct but what if the name of the method changes in the future, you would have to change the Main/Driver class code also, what if the Main/Driver class code is implemented by someone else and every time you change the method name in the Candidate class you have to notify that person. Can we change what happens when you do System.out.println(candidate) ?

Here is the code that is giving us the output.

This is just a sample of the object class with one method toString() which is called when we try an object in Java. As we mentioned at the beginning, all classes extend the Object class. If they already extend the Object class can they change/alter the behavior of the toString method. That should ring a bell, sounds like method overriding, right?

Let’s try that!

Now, lets execute our Main class again.

Well, we can now see some meaningful info about our object and it makes sense. Even code wise any class that wants to add its own string interpretation should override the toString method.

This is the real use case of one method of the Object class called toString.

Now, let’s take another look at the sample Object class.

In the toString method another method hashCode is called. What is this hashCode method?

This is what the hashCode method looks like in the object class. We will not go too much in details of the annotation used as of now but notice the native keyword, it means that the implementation of hashCode is in a low-level language like C/C++. We will explore the hashCode method in detail in another article but we should know that the hashCode method has its applications when your class’s object is used in HashMap or a HashSet to achieve constant time lookups.

Now, let’s move to another scenario where we will need to check if the same candidate has applied to the same job more than once, in that case we would like to check if two Candidate class objects are same.

Let’s check that

We have prepared two Candidate class objects that have the same id and same name, let’s check if these two are equal using the .equals method provided by the Object class. If you run the above code snippet this will be the output

You might be thinking, but wait the id and name properties are same for the two objects. But the .equals method doesn’t know properties of our class. Let’s see what the .equals method do

Well, the method is just comparing the objects by reference or memory address. Now as this method is in the Object and every class extends it, we can override it.

Now, we have overriden the .equals method and compared the id property manually. Lets run the Main class again where we compared the two objects using the .equals method which we now have overriden.

Here is the output.

We now get true as the output as we have now compared the id property ourselves.

Now in an ATS, we don’t want to keep candidates in the system forever. They should be removed once they get an offer, get rejected or withdraw the application. We can’t manually remove the candidates based on the application status. As Java has a garbage collector, we can leverage that. Lets try to assign a Candidate class reference to null . Once we do that we would ideally need to do some extra work, most important would be notifying the candidate that he is no longer in the application pool and can apply to other open positions.

Where would you do that? If the Main/Driver class allocates a reference to null, the Driver/Main class should not be responsible for notifying the candidate, as, if there are multiple references set to null, due to various reasons(accepted, rejected, withdrawn), how to decide which candidate gets the proper response from the ATS?

What if we leave that to the Candidate class itself? Yes, the Object class has a method called finalize which can be implemented to certain tasks when an object is marked eligible for removal by the garbage collector. Let’s try to set a reference to null and try to call the garage collector in our Main class.

Here is its output.

Now, lets override the finalize method in the Candidate class.

To see this in action, let’s see a slight modification in the Main class.

We have allocated both references to null and called System.gc which suggests the JVM to run the garbage collector, but it doesn’t guarantee it. We will still run the Main class. When you run this code on your machines, the output will vary, here are a few variations.

In the first output, the finalize method was called first then Main class’s last line was executed, while in the second output, Main class’s last line was executed and then finalize was called for the 2 objects. Garbage Collector is non-deterministic in behavior.

You should also know that finalize is deprecated and itself if marked for removal :)

We will see reasons for that in another article.

These are the few methods in the Object class that can be overriden, but there is one more which can be overriden and more which cannot be overriden. If you know them, comment down below!

We will see those in detail in another article.

That’s it for this article. If you notice any mistakes that need corrections, feel free to comment them down below, I would love to address them.

Thanks for reading and happy coding!