Sunday, June 15, 2025

equals()

Now that we’ve created plenty of objects in Java and we’ve seen several data structures, we need to address a very large elephant in the room. I still haven’t told you how to check—easily, anyway—if two objects are “equal”, or what that means about a non-primitive, anyway.


Let’s start with the easy stuff—equality in primitives:

  • Integers are equal if their values are the same. 2 is equal to 2, but not to 327.
  • Doubles are equal if their values are the same: 3.14159 is equal to 3.14159, but not 3.15
  • And so on with shorts, bytes, longs, and floats
  • Characters are equal if they’re the same: ‘A’ is equal to ‘A’, but not ‘a’
  • Booleans are equal if they have the same value: false and false are equal, but false and true are not

Because they’re primitives, we learned we make comparisons with == (or !=, or whatever you want), since single = is used for assignment, so it can’t be lifted directly from math.

Objects are a little trickier. Equality by = still works, but there’s a catch. That checks if the memory addresses of two things are the same.  

So let’s suppose I have

String s1 = “hello”;

and


String s2 = “hello”;

 

Intuitively, there’s nothing different about s1 and s2. Both have exactly the same contents, an all-lowercase “hello” and nothing else.

But Java doesn’t see those as the same. s1 and s2 are different objects, stored in different places in memory, so their addresses are different, so s1==s2 would return false.

Now, in Java, we have a concept called aliasing.

Let’s say we do the following instead:

  • We create String s1 = “hello”; as before
  • But now, instead, we sat String s2 = s1;

Now, s2 is an alias of s1—s2 and s1 point to the same location in memory.

Suppose s2 then changes to “oi” instead. After this change, s1 points to “hello” (nothing changed), but s2 points to the brand new, different, in a different place, “oi”.

Let’s see that in action:

class Main {

    public static void main(String[] args) {

        String s1 = "hello";

// s2 points to the same place
String s2 = s1;

// hello       
System.out.println(s1);

// hello       
System.out.println(s2);

s2 = "oi";

// hello      
System.out.println(s1);

// oi       
System.out.println(s2);

    }

}

While it is still true that s1 and s2 point to the same place (before a new String is created, when s2 becomes “oi”), then s1==s2 would return true. But once the value of s2 changes, s1 and s2 no longer point to the same place, so s1==s2 would return false.  

Without aliasing, let’s consider the following scenario:

class Main {

    public static void main(String[] args) {

       

        String s1 = "hello";

        String s2 = "hello";

    }

}

In this case, is it true that s1==s2? Again, it feels like it should be true, but it’s false. By not saying s2=s1 as before, we are telling the computer that s1 and s2 are completely different objects, which we don’t intend to point to the same place in memory.

But we, wise humans who can read and know there’s more to the story, know the contents of the Strings are identical. Surely, then, there must be a way to check “even if the Strings don’t have the same address, are they equal in the sense that every character in one matches every character in the other?”

In fact, most of the time, you won’t care where things are stored in memory—after all, this isn’t C, so you aren’t allocating and freeing memory yourself, leaving that all to Java. What you’ll most likely want to know is whether the contents of a String are equal to that of another.

For that, we have the equals() method.

equals() isn’t unique to Strings, however. All objects—whether you created them, or someone else did, like String, or Random, or anything else—since they descend from the Object class (this is never stated explicitly, but it’s always true) inherit some of its methods. If you didn’t when we covered this fact when we looked at inheritance, then I suggest you look at the API documentation now. You’ll see that one of the methods in Object—thus inherited by every class, whether you use it or not—is the equals() method.

Recall the difference between overloading and overriding. Overloading is, for example, having an add() method that takes in 2 ints, another that takes in 2 doubles, another for 2 bytes, another for 2 longs, etc. Overriding, meanwhile, is changing the behavior of whatever you inherited, to better fit the needs of the child class. The default behavior is exactly what we saw earlier, with the equality (or not) of the addresses of “hi”.  

Since you’ve inherited that method from Object, you can, of course, override it to better suit your needs. String has done this already, and String’s equals() checks if the contents are equal. If every character in two Strings is the same, they are the same.

Note that, for example, “I LIKE PIZZA” is not equal to “I like pizza” or “i like pizza” or “ I LIKE PIZZA” or “I LIKE PIZZA ”. “I LIKE PIZZA” matches exactly only with “I LIKE PIZZA” and only in that instance would equals() return true.

Suppose you have a Student class and you want to check if two Students are the same. Suppose a Student has an ID, name, date of birth, year, GPA, and major. You could certainly plausibly override the equals() in Student to return true if and only if all those fields—ID, name, date of birth, year, GPA, and major—all are exactly the same. You may have "the same" Student in different places in memory, so what you care about are the stats, not the memory addresses. If they are all the same, then presumably you and your program would both conclude that the Students are the same. But if any of those properties mismatch, you’re looking at different Students.

How you define equals() is largely up to you. Just make sure that whatever criteria you pick make sense and are specific enough to get the definition of equality you want—and be aware that, if, in the course of comparing Puppy objects, you need to check if their owner Strings are the same, then leading and trailing spaces, or capitalization, all matter when detetmining equality or not. 

No comments:

Post a Comment

Switch

 Other than if/if-else/if-else if-else and the ternary operator, there is yet another common and important conditional expression in Java th...