Java in Notes/Domino Explained: Testing for equality


When comparing strings in Java you are actually comparing the objects and not the string itself. If you read my resent post on common operators in Java you would have read the following explanation for the == operator: “Test for object reference equality or equality of primitive data types.”

So what does “object reference equality” mean? It means that the == operator will object return true if the references are pointing to the same object. It doesn’t look inside the object or make any assertations as to whether the “real world” value of the objects are the same. The below example should clarify:

String s1 = new String("lekkimworld");
String s2 = new String("lekkimworld");
String s3 = s1;
String s4 = s2;
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s2 == s4);
System.out.println(s1 == s4);

The above code will provide the following output:

false
true
true
false

The above output shows you that the string s1 and s2 are different references and hence are not equal in the == sense. Once we create s3 and s4 pointing to s1 and s2 respectively, == return true since s1/s3 and s2/s4 are pointing to the same object.

So the rule of thumb is: Only use == to compare primitive datatypes (int, long etc.) or objects if you need to see if it is the same object (being referenced) – not necessarily the same value.

It’s actually a more general problem

To alleviate the problem of comparing references all objects in Java has a method called equals. The method is inherited from java.lang.Object, from which all classes inherit, and if not overridden by the class will revert to using the == operator for comparisons.

The String class overrides the equals() method to test whether the value of the strings are the same. Since the equals() method is case sensitive the String class also have an equalsIgnoreCase() method. Let’s see the equals() method in action:

String s1 = new String("lekkimworld");
String s2 = new String("lekkimworld");
String s3 = s1;
String s4 = s2;
System.out.println(s1.equals(s2));
System.out.println(s1.equals(s3));
System.out.println(s2.equals(s4));
System.out.println(s1.equals(s4));

The above code will provide the following output:

true
true
true
true

Notice the difference between the output of this code and the code in the first example. Now we test the “real world” value of the objects hence all comparisons are true.

The concept of the equals() method carry across to custom objects you write. If you plan to test if two objects are the same, which is almost always the case, you should override the equals() method. Let me show how this works in the case of a simple Employee class. I have decided that comparisons should be based on the employee id and not the name:

public class Employee {
   private String id = null;
   private String firstname = null;
   private String lastname = null;

   public Employee(String first, String last, String id) {
      this.firstname = first;
      this.lastname = last;
      this.id = id;
   }

   public boolean equals(Object obj) {
      // if null it can never be the same
      if (null == obj) return  false;

      // if of another type if can never be the same
      if (!(obj instanceof Employee)) return false;

      // same class - compare
      return ((Employee)obj).getId().equals(this.id);
   }

   public String getFirstname() {
      return firstname;
   }
   public String getId() {
      return id;
   }
   public String getLastname() {
      return lastname;
   }
}

Notice how I start by verifying that the supplied object isn’t null and that it is of the same type. If not the objects can never be equal. If both the supplied object passes both tests I use the equals() method of the String class to compare the ids. Below is a snippet of code to illustrate:

Employee e1 = new Employee("John", "Doe", "111");
Employee e2 = new Employee("Jane", "Doe", "222");
Employee e3 = new Employee("John", "Doe", "111");
System.out.println(e1 == e2);
System.out.println(e1 == e3);
System.out.println(e1.equals(null));
System.out.println(e1.equals(new String("lekkimworld")));
System.out.println(e1.equals(e2));
System.out.println(e1.equals(e3));

…and the output

false
false
false
false
false
true

Pay special notice to the fact that e1 == e3 returns false since it is different objects. Only our own custom equals() method will return true.

Did you notice? – the caveat about strings in Java

The astute reader might have noticed that I used the new keyword to create new strings in the first example instead of the “normal” approach:

String s1 = new String("lekkimworld");
instead of
String s1 = "lekkimworld";

The result will actually be quite different if you use the latter approach instead:

String s1 = "lekkimworld";
String s2 = "lekkimworld";
String s3 = s1;
String s4 = s2;
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s2 == s4);
System.out.println(s1 == s4);

The above code will produce the following output:

true
true
true
true

Isn’t that wierd? To understand it we need to dive a little deeper into how strings are managed in Java.

How strings are managed in Java

In Java strings are imutable that is they cannot change once created. If you concatenate two strings the result will be the creation of a new third object:

String full = "Mikkel " + "Heisterberg";

The problem is actually due to the fact that we cannot redimension arrays in Java (the char[] array that backs the string object) as described in an earlier post. You can imagine that concatenating a lot of strings can yield quite a lot of objects. To alleviate this potential performance problem you can use the java.lang.StringBuffer class.

Since strings are used so much and because they are imutable the String class holds an internal pool of created Strings (a “cache” if you will). It can hold on to the previously created strings since the imutable property makes the class thread-safe and hence safe to reuse. It also means that if you create the same string twice the String class will return the cached object the second time around. This isn’t the case when using the new keyword which is why there is a difference between the examples above.

So what do you do if the returned string was created using the new keyword but you really want the one from the string pool? Well you’re in luck – you can use the intern() method of the String class:

String s1 = "lekkimworld";
String s2 = "lekkimworld";
String s3 = new String("lekkimworld");
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s2 == s3);

String s4 = s3.intern();
System.out.println(s1 == s4);

This will produce the following output:

true
false
false
true

The last line is true since the intern() method returns the copy of “lekkimworld” from the string pool and not the one from the s3 object. Normally you don’t have to spend too much time thinking about the intern() method but now you know… 🙂

Happy comparisons…