Posts tagged POJO
Objects: equals() + hashCode() + toString() methods
0I have finally decided to start my own blog, my first post will be about some basics of Java objects.
During my years of experience in the field I have come across peers from a wide variety of seniority levels that do not know or do not know how to apply correctly basic concepts.
Here I will talk about the need of overriding the default implementations of the methods as follows:
- toString()
- equals()
- hashCode()
These methods are inherited from the parent of all parents the java.lang.Object class.
Let’s suppose that we have a SoccerPlayer.java class defined as follows:
public class SoccerPlayer {
private String name;
private String lastName;
private int jerseyNumber;
private String teamName;
public String getName() {
return name;
}
public String getLastName() {
return lastName;
}
public int getJerseyNumber() {
return jerseyNumber;
}
public String getTeamName() {
return teamName;
}
public void setName(String name) {
this.name = name;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setJerseyNumber(int jerseyNumber) {
this.jerseyNumber = jerseyNumber;
}
public void setTeamName(String teamName) {
this.teamName = teamName;
}
}
Notice how we use the access modifier keyword private and provide the set/get access methods with the public modifier. This is a best practice for two reasons:
- To make sure that other objects in your program are not allowed to change the data member variables directly
- To prohibit a subclass from inheriting (possibly a very business critical data member)
Enough fundamentals, back to the goal of the post. Let’s create now a class named Starter.java as follows:
public class Starter {
public static void main(String[] args){
SoccerPlayer sp = new SoccerPlayer();
sp.setName("Zinedine");
sp.setLastName("Zidane");
sp.setJerseyNumber(5);
sp.setTeamName("Real Madrid");
System.out.println(sp);
}
}
We will get something like this in the console: SoccerPlayer@19821f which is the memory address of the object, very useless.
Overriding the toString() method
Add the following snippet to the SoccerPlayer.java
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("SoccerPlayer [jerseyNumber=");
builder.append(jerseyNumber);
builder.append(", lastName=");
builder.append(lastName);
builder.append(", name=");
builder.append(name);
builder.append(", teamName=");
builder.append(teamName);
builder.append("]");
return builder.toString();
}
Notice two things here:
- @Override annotation (Annotations were introduced in Java 5.0). This tells the compiler that you are trying to override this method. It is helpful because if you miss type it, or miss type the method signature then the compiler says oh…wait a minute this is not an existing method in the base class, please check the signature. That’s all, nothing major.
- StringBuilder use over a StringBuffer or a regular String object: StringBuilder class was also introduced in Java 1.5, it’s an exact copy of StringBuffer but its methods are not thread safe, therefore there is a gain in performance when using StringBuilder over StringBuffer.
Run your main class again by typing the command java Starter. You should get a String representation of the object like this:
SoccerPlayer [jerseyNumber=5, lastName=Zidane, name=Zinedine, teamName=Real Madrid]
Over the years I have seen many POJOS (Plain Old Java Objects), that is entitty objects with sets and gets that did not have the toString() method overridden, they were not printing it of course but I still think is a must have at least for debuging and testing purposes
Overriding the equals() method
We know that two compare primitive types you use the == operator. But how do you compare a more complex data type? You have to override the default implementation of the equals() method provided by the superclass java.lang.Object. This is what a typical implementation of the equals() method will look like for the SoccerPlayer class:
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SoccerPlayer other = (SoccerPlayer) obj;
if (jerseyNumber != other.jerseyNumber)
return false;
if (lastName == null) {
if (other.lastName != null)
return false;
} else if (!lastName.equals(other.lastName))
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
if (teamName == null) {
if (other.teamName != null)
return false;
} else if (!teamName.equals(other.teamName))
return false;
return true;
}
All these code is saying is the following:
The first if states that if the object passed in points to same memory reference as the object being compared to (reflection), return true.
The 2nd and 3rd if statement states that if the object passed in is null or is not of the same type as to the object that we are comparing against , return false.
The rest of the if statements check property by property and if one of them does not match, return false. Otherwise, if all property values match return true.
Let’s test it by adding the following code to the Starter.java:
SoccerPlayer sp2 = new SoccerPlayer();
sp2.setName("Zinedine");
sp2.setLastName("Zidane");
sp2.setJerseyNumber(5);
sp2.setTeamName("Real Madrid");
if(sp == sp2){
System.out.println("The 2 objects are equal");
}else{
System.out.println("The 2 objects are not equal");
}
if(sp.equals(sp2)){
System.out.println("The two objects are equal");
}else{
System.out.println("The two objects are not equal");
}
The first if statement using the == operator will fall into the else block because it is trying to compare the memory address instead of the actual object properties.
The second if statement uses the equals() method which will call the overriden version that we provided will return true. See the output results in the command line:
The 2 objects are not equal
The two objects are equal
Alert!!: The equals method DOES NOT have to compare attribute per attribute to determine whether two objects are equal or not. This depends on your business rules. Say for example that the players would have some sort of unique identifier like an SSN or other known ID set up by FIFA, we could have used that ID field in our equals method to determine whether two players are the same or not and avoid comparing field by field. You are the best one to make that call based on your specs.
Overriding the hashCode() method
The hashCode() method seems to be this unforgotten method that everybody overrides with the auto generated code but without actually knowing what is it used for. The hashCode() is used to increase the search performance of large collections of objects. The hashCode() returns an integer, this integer value determines how an object is stored and to help locate the object once in the collection. By contract if two objects are equal using the equals() method then they must return the same hash code value. However, this does not imply that if two objects are not equal then they should have different hash code values., ultimately it depends on how efficient your algorithm is. Because it is possible to have more than one object with the same hash code you will have to do two steps when searching for an object. The steps are as follows:
- Find the right position in the “Hash” Collection using the hashCode() method (to start the search from there)
- Iterate through the list of elements at that position using the equals() method to find what you are looking for
This is what the default implementation of a hashCode() override looks like from auto generated code from my favorite IDE IntelliJ:
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + lastName.hashCode();
result = 31 * result + jerseyNumber;
result = 31 * result + teamName.hashCode();
return result;
}
Usage:
Java based ETL tools such as Talend make extensive use of the hashCode() method in order to make the two steps above work efficiently when processing/transformation of data among components. In these type of tools is very important to have a decent hash code algorithm as it is going to be a critical component in determining the run time speed of the job. You do not have to worry about writing a hash code algorithm though as it in this GUI tools it will be automatically generated for you .
Alert!!:You can have a hashCode() method that always returns the same integer ,this is valid, but it will be very inefficient as you are going to be putting every object inside the same bucket and the search will be expensive as it will be a linear search. Remember that the ultimate goal is to come up with an algorithm that will put all the objects as dispersed as possible within a collection.



