One of the most important and fundamental aspects to Java is the Java Object. Since this topic is central to the entire programming language, let's talk about the things you SHOULD know about Objects!
Okay, so what should you know? Well, first I want to talk about why the Java Object is so fundamental to the programming language.
The Java Object
We've already talked about many examples of Objects in Java. These are what I would define as a “noun” in any sentence that would define a business problem. These “nouns” could include words like, User, Library, Vehicle, but what's one Object we haven't yet mentioned? The answer is easier than you may think. It's Object! Did you know that you could set a variable's type in Java to be Object
? Well, you can… and it's quite handy!
Everything is an Object
In Java, any object you create (User, Library, Vehicle) is actually of type Object
. This concept is what is so fundamental to understand. So let's say you create the object User
, it could look like this:
public class User
{
private String username;
private String password;
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
}
Now, there's nothing in this code that would indicate that this User
object is actually of type Object
. Usually, to determine if one particular object inherits properties of another object, you would look to see if your object extends another object. But in the case of our User
there is no extends code. So what the heck?!
Well, since all Objects in Java inherit from the Object type, there's no need to explicitly show this in our code. If you don't believe me, try creating a new Object with your STS IDE. Here's a screenshot from when I made the User
object above:
Notice that the super class of this User
object I'm creating automatically defaults to Object
? Well, that's because all objects in Java inherit from Object
!
Seriously, everything is an Object?
Okay so you believe me, everything inherits from Object
, so you may ask “Are there any exceptions to this rule?”. Well, obviously! Aren't there exceptions to every rule that exist in this crazy world?
The exceptions are primitive data types, I've covered this topic in a previous post. Primitives do not inherit from the Object
type in Java, but that's it… that's the only exception to the rule. Everything else in Java either directly (or indirectly) inherits from the Object
type.
What are the implications to inheriting from Object
What's interesting to note, is that since every object in Java inherits from the Object
type, that means that whatever methods are defined (as public
or protected
) on the Object
type should be available to any object that WE create, right? Right!
So what are these methods?
Excellent question, let's take a look at the User
object we created above. If we were to instantiate this User
and take a look at which methods could be invoked on the User
, what would we see? Well, we would expect to see the public
methods that we defined right? These would be:
setPassword()
getPassword()
setUsername()
getUsername()
But here's what we actually see:
There are a few methods there that we didn't create, these include:
equals()
getClass()
hashCode()
notify()
notifyAll()
toString()
wait()
wait(long timeout)
wait(long timeout, int nanos)
And if you notice in the screenshot above, on the far right of all these methods, is the word Object
. This is because all of these methods belong to the Object
type. Make sense? It's nice to know what's actually going on here right? So then the only other topic to cover is what all of these methods are used for… I don't want to dive into all of them in detail, so I'll get the ones I don't want to talk about out of the way right now.
Threading Methods
- notify()
- notifyAll()
- wait()
- wait(long timeout)
- wait(long timeout, int nanos)
All of these methods are related to threading, and I don't want to explain right now, as the threading topic is likely a whole series of posts. If you've ever heard of the term “Multi-threading”, it has to do with being able to simultaneously run multiple “tasks” all at once. The above methods help facilitate multi-threading. So although they are very interesting in their own rights, I don't think it would be useful to talk about them at this point. Let's look at the methods that remain:
equals()
This is a method you've probably seen before. We use it when comparing two String
s to each other. It's the method that we use when we compare any two objects with each other. But hang on a second, I thought we used the “==” operator to compare objects! You've seen it before:
int someNumber = 478;
int someOtherNumber = 983;
if (someNumber == someOtherNumber)
{
System.out.println("These numbers are equal");
}
else
{
System.out.println("These numbers are NOT equal");
}
So why are we talking about a equals()
method then? Well, remember when I said that primitive types are the exception to the rule? Well that means that primitive types don't inherit from the Object
type right? And if that's the case, then that means that primitive types don't have access to the methods that are defined within Object
, which includes equals()
!
Very interesting, so then if we want to compare two objects, then we just use the equals
method then right? This is true, but there's a bit of work that we'll need to do to ensure that when we do compare two objects, we get the expected result. Consider the following:
User user1 = new User();
User user2 = new User();
user1.setUsername("Trevor Page");
user1.setPassword("aPassword");
user2.setUsername("Trevor Page");
user2.setPassword("aPassword");
System.out.println("Are users equal? " + user1.equals(user2));
What would you expect to see as the output here? We are instantiating two User
objects, and we are assigning the exact same username and password to both User
s. Then we are just invoking the equals()
method to check if both User
s are equal. We would assume that since both the username and password are equal, that the result would be “true”. Here's the actual result:
Are users equal? false
Well what the heck! Why aren't they equal? Well, this comes down to inheritance and the default implementation of the equals()
method.
Now pay attention, because this is important!
If you do not override the default implementation of the equals()
method, then Java will default to the strictest implementation of an equals
comparison. And that is the “==” operator!
What does the “==” operator do!
The “==” operator (spoken as “equals equals operator”) compares two objects by their physical memory address. I have a good discussion on what memory is all about in Java in a previous post about Strings, so if you haven't already, please read the section entitled “Memory” in this post.
So how does this apply to our example above with the two User
s? Since we haven't overriden the equals()
method, it will compare the two objects using the “==” operator, which will compare their addresses in memory. Since I created two objects (via the new
keyword), this means we have two separate objects in two separate memory locations. Since they have two unique locations in memory, the equals()
comparison will return false.
How do we override the equals() method
In our User
s example we would like to compare two users to each other, but we don't want to compare the physical memory addresses, we want to define our own meaning of equals. So what in your mind would constitute equality among User
s? I think it would just be if two User
s had the same username! Hopefully in any web application, no two users would be allowed to have the same usernames. So if we have two User
objects, and they both have the same username, then we could consider them equal.
How do we accomplish this? We override the equals()
method on the User
object! So our new User
object would look like this:
public class User
{
private String username;
private String password;
public String getUsername()
{
return username;
}
public void setUsername(String username)
{
this.username = username;
}
public String getPassword()
{
return password;
}
public void setPassword(String password)
{
this.password = password;
}
@Override
public boolean equals(Object obj)
{
return this.username.equals(((User)obj).getUsername());
}
}
Notice the last section of our User
object has an @Override
over the equals()
method. Well, this is how we override the equals
method of the super class (the Object
class). This is the inheritance I was talking about. In Java we are allowed to “change” the behaviour of a method that we inherit from a parent Class. In this case, the parent Class of our User
object is Object
. So, because the Object
Class has an equals()
method, then that means we can change its behaviour in our child class (the User
Class). So, since we didn't like how the Object
‘s equals()
method worked, we override its functionality in our User
Class! And this is the code you see above.
So! This is fairly complex stuff if you're new to programming, so allow me to keep explaining. Let's take a look at the code where we actually compare the two User
s together, as this may shed some light on this subject…
User user1 = new User();
User user2 = new User();
user1.setUsername("Trevor Page");
user1.setPassword("aPassword");
user2.setUsername("Trevor Page");
user2.setPassword("aPassword");
System.out.println("Are users equal? " + user1.equals(user2));
This code will now output what we would expect:
Are users equal? true
Okay great, so maybe you're thinking “I kind of understand the concepts, but I really don't understand how that's reflected in the code!”. So let me try to explain what's happening.
I want you to concentrate on the most important part of the code above:
user1.equals(user2)
Remember that the User
definition (with the @Override equals code) is a Class that represents the blueprint for any User
object. So when we instantiate a User
object, each one will essentially have its own version of the @Override equals method defined in the User
Class.
This means that when we run that important code, we are saying:
Hey! user1
! Run your equals()
method, and pass in user2
as a parameter. So what we may actually see if we were to debug this is the following (I've commented out the actual code, and instead, replaced the code with what would essentially be replaced at runtime when Java runs the code):
@Override
//public boolean equals(Object obj)
public boolean equals(User user2)
{
// return this.username.equals(((User)obj).getUsername());
return user1.username.equals(user2.getUsername());
}
So, what really ends up happening is that we pass in the user2
object as a parameter. Now, what normally happens is the equals method takes an Object
as a parameter, but since a User
is an Object
, Java is okay with you doing this (inheritance). Then we say, this.username
, well this
just refers to whatever Object the Class represents. So in our case, since we invoked the equals()
method of user1
, that means we're inside of the user1
equals method, which means that when we say this
we really mean user1
! But then we say this.username.equals()
, doesn't that mean we just invoke the equals method on user1
again, resulting in an endless loop? No! We're invoking equals
on user1.username
… and username
is just a String
. So that's perfectly legitimate, as String
defines it's own equals()
method. Then we just pass in user2
username into the equals
method for the String comparison. Which we know that, if both String
s are the same, then it will return true!
Phew, that's some hardcore coding there! If you understand it, then that's amazing and I've done a great job of explaining the concepts. If you don't understand it, then you're probably part of the large portion of the new programmers on this planet. The topics that stem from Java's Object
class are quite complex as they require a solid understanding of Object Oriented Programming (more specifically inheritance and polymorphism). So, if you don't get it, take some time to re-read this tutorial and maybe try messing around with the coding examples I've given.
Loose ends
I have yet to talk about the other methods that come from the Object
class, such as hashCode()
and toString()
. So let me touch on these quickly.
toString()
This method is used to return a String
representation of our objects. The default implementation of this (from the Object
class) will just return a readable version of the physical memory address. So if I were to do a System.out
on the user1
object, I would get the following:
com.howtoprogramwithjava.business.User@7d8a992f
This means nothing to us, and it's not helpful, other than if you would like to check to see if two objects reference the same memory location. So that's why we can override the toString()
method to give something more meaningful. Let's override it! Please add the following method to our User
class
@Override
public String toString()
{
return "Username: " + this.username + ", password: " + this.password;
}
Now, if we were to invoke user1.toString()
, we wouldn't get a meaningless representation of a memory location, we would get:
Username: Trevor Page, password: aPassword
Hey! That actually makes some sense, and it's useful! So, that's a quick rundown on the toString()
method.
hashCode()
This is a more advanced topic that revolves around the use of HashMap
s, we've talked about what Map
s are back in one of my first posts about data types. So for the sake of completion (and for the sake of not overloading your brain), I'll just say that generally when you override the equals
method, it's good practice to also override the hashCode()
method. This is so that if your objects are put into a Map
, Java will be able to “store” them nicely inside of your HashMap
. How do you override hashCode()
? Also slightly advanced, but for now try this… In your STS, try right-clicking in your User
code then:
Source -> Override/Implement Methods -> Choose HashCode -> OK
Summary
We've covered quite a lot of topics in this Java Object post. So I encourage you to take a break, try to digest this information, and perhaps come BACK and re-read everything so you have a good understanding of these concepts. As I've said, these are more advanced topics, but they are critical to understand if you want to get the hang of programming in Java.
So please, by all means, if you are unclear on something, leave a question in the comments section and I'll get back to you… I promise! I love to help you guys out, so that means I love answering questions. So seriously, scroll down right now, leave a comment!
Take care everyone and I look forward to hearing from you :)
Bonus Q&A
One reader was confused by the @Override
of the equals()
method, so I tried my best to answer the question in as much detail as I could in the video below, I hope this helps others as well: