
Java programmer expert
In an object-oriented programming language like Java, comparing objects to each other or to primitive types is a fundamental yet often misunderstood task. While experienced developers handle this routinely, novice programmers frequently struggle with when to use the equality operators (== and !=) versus methods like equals() or compareTo().
Object comparison is the process of checking whether or not two objects are the same, based on data or references. In Java, objects are created from classes, and each object has its own set of data and defined behaviour. However, we can also compare whether the objects occur at the same location in memory, and if so, they are not two objects, but one and the same.
In this article, we will use practical examples to show how to correctly compare objects in Java, either based on the data in the objects or their references. You will also learn how the equals() method is implemented and works, and we will also mention the importance of implementing the hashCode() method to efficiently compare objects using the hash algorithm.
In Java, primitive types such as int, double, boolean, etc. are not objects, but basic data types. When comparing objects with primitive types, Java automatically converts the corresponding wrapper object of a class to its primitive type using the extension primitive conversion (§5.1.2). The following rules are specified:
Example:
Integer I = new Integer(5);
int i = 5;
System.out.println(I == i);
// => true
System.out.println(I.equals(i));
// => true
If we compare an object with its primitive type, either using the == operator or the equals method, it is equivalent, and if the values are the same, the result is a boolean true.
The identity of a variable (also called reference equality) is defined by the reference it has. If two variables have the same reference, they are identical. This is controlled by the == operator. It is important to remember that the == operator in Java is used to compare the references of two objects, not their contents. So it checks whether both references point to exactly the same object in memory (or its address).
Example:
String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1 == s2);
// => false
In this example, both strings have the same text, but since we are comparing references (memory addresses of two different instances), the result is understandably a boolean value of false.
Example:
String s1 = new String("Java");
String s2 = s1;
System.out.println(s1 == s2);
// => true
However, if instead of creating an instance in the second string, we copy the reference to the first string when comparing references, the result is true.
The equality of a variable is defined by the value it refers to. If two variables refer to the same value, they are equal. In Java, this is checked using the equals() method. It is part of the Object class, which is the parent class of all Java classes. This means that we can use equals in Java to compare the data contents of any two objects.
Example:
String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1.equals(s2));
// => true
The example shows how to compare 2 different instances of the String type with the same text. The result of the equals() call returns the boolean value true. If we had a copied reference to the same object in the previous example, the result of comparing the values of the same object would also be true.
Example:
String s1 = new String("Java");
String s2 = s1;
System.out.println(s1.equals(s2));
// => true
So far we have used simple objects in the examples that the Java authors have provided in the libraries. Now let’s try to create our own object and have a look at the equals() method.
Example:
Shape class
package pack;
public class Shape {
String colour;
public Shape(String colour) {
this.colour = colour;
}
public String getColour() {
return colour;
}
public void setColour(String colour) {
this.colour = colour;
}
}
Circle class
package pack;
public class Circle extends Shape {
double radius;
public Circle (String colour, double radius) {
super(colour);
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
}
We have created a Circle class that inherits from the parent Shape class. We will now create two instances with the same data and compare their references and values using the equals method.
Circle c1 = new Circle("green", 1);
Circle c2 = new Circle("green", 1);
System.out.println(c1 != c2);
// => true
System.out.println(c1.equals(c2));
// => false
The fact that the references to the two different instances are different is what we expected. The surprise, however, may be that even though both objects have the same attribute values, the equals method returns false, meaning that the values of the two objects are different. This is because the default equals implementation in the Object class, which every object inherits, compares the memory addresses of objects, so it works just like the == operator. So we need to rewrite this method to define what equality means for our objects. But first we need to know the rules that the Equals method follows.
Suppose we have defined x, y, z references. Then a correctly implemented equals method should have the following properties: Reflexivity: for any non-zero reference x, x.equals(x) should return true. Symmetry: for all non-zero references x and y, x.equals(y) should return true if and only if y.equals(x) returns true. Transitivity: for all non-zero references x, y and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true. Consistency: for any non-zero references x and y, multiple invocations of x.equals(y) will consistently return the same true or false values in sequence, provided that no information used in the equals comparison of the objects is changed. For any non-null reference x, x.equals(null) should return false.
Now that we know the rules for equals, let’s implement our own version of equals for the Circle class.
@Override
public boolean equals(Object o) {
// self check
if (this == o) return true;
// Null check
if (o == null) return false;
// Type check and cast
if (getClass() != o.getClass())
return false;
Circle circle = (Circle) o;
// Field comparison
return Objects.equals(radius, circle.radius)
&& Objects.equals(colour, circle.colour);
}
We’ll check that both instances contain the same values and that the equals method works correctly and returns true.
Circle c1 = new Circle("green", 1);
Circle c2 = new Circle("green", 1);
System.out.println(c1.equals(c2));
// => true
In our implementation of equals, we first check that we’re comparing the object to itself, and if we are, the result is true. Then we check that the reference to the second object to which we are comparing the first object is valid (i.e. non-zero), and if it is not, we return false. No instance should be null, and this line ensures that we don’t get a NullPointerException when accessing attributes later. Before comparing the attributes, we test that the two objects being compared belong to the same class. This also ensures that even if the Circle class is separated from the Shape class, comparing these classes will always return false.
A common practice in Java is that when we override the original implementation of equals(), we should also override the hashCode() method, since both methods work together, especially when dealing with collections of objects. The connection between the two methods is that if the equals method determines that the two objects are the same, then they should also compute the same hash code. Without a correct implementation of the hash code, the use of collections that depend on it, such as HashSet and HashMap, will not work correctly. Let’s take a look at our Circle class to see how we should correctly overload the hashCode() method.
@Override
public int hashCode() {
return Objects.hash(radius, colour);
}
In Java, comparing two objects is not as simple as comparing two primitive data types. It requires a deeper understanding of the structure of objects and how they are stored in memory. The equals() method is the primary method for comparing two objects in Java. When we override the equals() method, we must also override the hashcode() method to ensure that two identical objects have the same hash code. By using the equals() and hashcode() methods, we can effectively compare two objects based on their internal state rather than their location in memory.
If you’re a Java developer looking for a job, check out our employee benefits and respond to our job offers!