Hi! Today we will talk about a very important and interesting topic, namely, comparing objects with objects.
So in Java, when exactly would object A be equal to object B?
Let's try writing an example:
public class Car {
String model;
int maxSpeed;
public static void main(String[] args) {
Car car1 = new Car();
car1.model = "Ferrari";
car1.maxSpeed = 300;
Car car2 = new Car();
car2.model = "Ferrari";
car2.maxSpeed = 300;
System.out.println(car1 == car2);
}
}
Console output:
false
Wait, stop. Why is it that these two cars are not equal? We assigned them the same properties, but the result of the comparison is false.
The answer is simple. The ==
operator compares object references, not object properties. Two objects could even have 500 fields with identical values, but comparing them would still yield false. After all, references car1
and car2
point to two different objects, i.e. to two different addresses.
Imagine a situation where you're comparing people. Certainly, somewhere in the world, there is a person who shares your same name, eye color, age, height, hair color, etc. That makes you similar in many respects, but you're still not twins — and you're obviously not the same person.
The ==
operator uses approximately this same logic when we use it to compare two objects.
But what if you need your program to use different logic?
For example, suppose your program performs DNA analysis. It compares the genetic code of two people and determines whether they are twins.
public class Man {
int geneticCode;
public static void main(String[] args) {
Man man1 = new Man();
man1.geneticCode = 1111222233;
Man man2 = new Man();
man2.geneticCode = 1111222233;
System.out.println(man1 == man2);
}
}
Console output:
false
We get the same logical result (because we didn't change much), but now that logic is no good! After all, in real life, DNA analysis should give us a 100% guarantee that we've got twins standing in front of us. But our program and the ==
operator tell us the opposite. How do we change this behavior and make sure the program outputs the correct result when the DNA matches?
Java has a special method for this: equals()
.
Like the toString()
method, which we discussed earlier, equals()
belongs to the Object
class — the most important class in Java, the class from which all other classes derive.
But equals()
doesn't change our program's behavior all by
public class Man {
String geneticCode;
public static void main(String[] args) {
Man man1 = new Man();
man1.geneticCode = "111122223333";
Man man2 = new Man();
man2.geneticCode = "111122223333";
System.out.println(man1.equals(man2));
}
}
Console output:
false
Exactly the same result, so what do we need this method for? :/
It's all simple. The issue here is that we're currently using this method as it is implemented in the Object
class. And if we go into the code of the Object
class and look at the method's implementation, this is what we'll see:
public boolean equals(Object obj) {
return (this == obj);
}
That's the reason why the program's behavior hasn't changed! The very same ==
operator (which compares references) is used inside the equals()
method of Object
class.
But the trick with this method is that we can override it. To override means to write your own equals()
method in our Man
class, giving it the behavior we need!
At present, we don't like the fact that man1.equals(man2)
is essentially equivalent to man1 == man2
.
Here's what we'll do in this situation:
public class Man {
int dnaCode;
public boolean equals(Man man) {
return this.dnaCode == man.dnaCode;
}
public static void main(String[] args) {
Man man1 = new Man();
man1.dnaCode = 1111222233;
Man man2 = new Man();
man2.dnaCode = 1111222233;
System.out.println(man1.equals(man2));
}
}
Console output:
true
Now we get an entirely different result! By writing our own equals()
method and using it instead of the standard one, we've produced the correct behavior: Now if two people have the same DNA, the program reports "DNA analysis has proven they are twins" and returns true!
By overriding the equals()
method in your classes, you can easily create whatever object comparison logic you need.
Comparing strings
Why are we considering string comparisons separately from everything else? The reality is that strings are a subject in their own right in programming.
First, if you take all the Java programs ever written, you'll find that about 25% of the objects in them are strings. So this topic is very important.
Second, the process of comparing strings is really very different from other objects.
Consider a simple example:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1 == s2);
}
}
Console output:
false
But why did we get false? After all, the strings are exactly the same, word for word :/
You might have guessed the reason: it's because the ==
operator compares references! Clearly, s1
and s2
have different addresses in memory. If you thought of that, then let's rework our example:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = "CodeGym is the best website for learning Java!";
System.out.println(s1 == s2);
}
}
Now we again have two references, but the result is the exact opposite:
Console output:
true
Helplessly confused? Let's figure out what's going on.
The ==
operator really does compare memory addresses. This is always true and you don't need to doubt it. That means that if s1 == s2
returns true, then these two strings have the same address. And indeed this is true!
It's time to introduce you to a special area of memory for storing strings: the string pool
The string pool is an area for storing all the string values you create in your program.
Why was it created? As we said before, strings represent a huge percentage of all objects. Any large program creates a lot of strings. The string pool was created to save memory: strings are placed there and then subsequently created strings refer to the same area of memory—there is no need to allocate additional memory each time.
Every time you write String = "........"
the program checks if there is an identical string in the string pool. If there is, then a new string won't be created. And the new reference will point to the same address in the string pool (where the identical string is located).
So when we wrote
String s1 = "CodeGym is the best website for learning Java!";
String s2 = "CodeGym is the best website for learning Java!";
s2
points to the same place as s1
. The first statement creates a new string in the string pool. The second statement simply refers to the same area of memory as s1
.
You could make another 500 identical strings and the result wouldn't change.
Wait a minute. If that's true, then why didn't this example work before?
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1 == s2);
}
}
I think your intuition has already told you the reason =) Try to guess before reading further.
You can see that these two strings were declared in different ways. One with the new operator, and the other without it. Herein lies the reason. When the new
operator is used to create an object, it forcefully allocates a new area of memory for the object. And a string created using new
doesn't end up in the string pool — it becomes a separate object, even if its text perfectly matches a string in the string pool.
That is if we write the following code:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = "CodeGym is the best website for learning Java!";
String s3 = new String("CodeGym is the best website for learning Java!");
}
}
In memory, it looks like this:
And every time you create a new object using new
, a new area of memory is allocated, even if the text inside the new string is the same!
It seems we've figured out the ==
operator. But what about our new acquaintance, the equals()
method?
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1.equals(s2));
}
}
Console output:
true
Interesting. We are certain that s1
and s2
point to different areas in memory. But the equals()
method still tells us they are equal. Why?
Remember we previously said that the equals()
method could be overridden to compare objects however we want?
That's just what they've done with the String
class. It overrides the equals()
method. And instead of comparing references, it compares the sequence of characters in the strings. If the text is the same, then it doesn't matter how they were created or where they are stored: whether in the string pool or a separate area of memory. The result of the comparison will be true.
By the way, Java lets you perform case-insensitive string comparisons.
Normally, if one of the strings has all uppercase letters, then the result of the comparison will be false:
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CODEGYM IS THE BEST WEBSITE FOR LEARNING JAVA!");
System.out.println(s1.equals(s2));
}
}
Console output:
false
For case-insensitive comparisons, the String
class has the equalsIgnoreCase()
method. You can use it if you only care about comparing the sequence of specific characters rather than the letter case. For example, this could be helpful when comparing two email addresses:
public class Main {
public static void main(String[] args) {
String address1 = "2311 Broadway Street, San Francisco";
String address2 = new String("2311 BROADWAY STREET, SAN FRANCISCO");
System.out.println(address1.equalsIgnoreCase(address2));
}
}
In this case, we're obviously talking about the same address, so it makes sense to use the equalsIgnoreCase()
method.
The String.intern() method
The String
class has one more tricky method: intern()
;
The intern()
method works directly with the string pool. If you call the intern()
method on some string:
- It checks whether there is a matching string in the string pool
- If there is, it returns the reference to the string in the pool
- If not, it adds the string to the string pool and returns a reference to it.
After using the intern()
method on a string reference obtained using new
, we can use the ==
operator to compare it with a string reference from the string pool.
public class Main {
public static void main(String[] args) {
String s1 = "CodeGym is the best website for learning Java!";
String s2 = new String("CodeGym is the best website for learning Java!");
System.out.println(s1 == s2.intern());
}
}
Console output:
true
When we compared these strings previously without intern()
, the result was false. Now the intern()
method checks whether the string "CodeGym is the best site for learning Java!" is in the string pool. Of course, it is: we created it with
String s1 = "CodeGym is the best website for learning Java!";
We check whether the s1
and the reference returned by s2.intern()
point to the same area of memory. And of course, they do :)
In summary, memorize and apply this important rule:
ALWAYS use the equals()
method to compare strings! When comparing strings, we almost always mean to compare their characters rather than references, areas of memory, or anything else. The equals()
method does exactly what you need.`
Was published on CodeGym blog .