Why == Doesn't equal equals()

Table of Contents (of this tutorial)

Crucial preliminary remark.

Basic Ideas.

This Won't Do, Will It? Overriding java.lang.equals()

Ahem. But Now We Have to Override hashCode() Too

AHEM: This has been wrong since creation in August 2005. Corrected March 5, 2006, after mail from Sergio Benavides, a computer engineering student at Florida International University.

 

Preliminary remark: this stuff matters. You can fake it for a while, writing == and hoping for the best. If the compiler whines, try equals() and see what happens. If the compiler doesn't whine, you may occasionally even accomplish what you were trying to do, but that will be  rare, and you won't know why it worked. Not good. Give this a focused half an hour, and you won't  be that pathetic. You will still have things to learn, but you'll be on solid footing for quite a while. Until we get to Collections, approximately. And I may do a tutorial on the wrapper classes for the primitives. (Until I do, look them up. Not rocket science.)

Basic Ideas

The crucial starting point is that in Java there are primitives, eight of them (boolean, byte, char, double, float. int, long, and short) and absolutely everything else is an object or a reference to an object. (If you think String is a primitive, get over it. It's a class, from which you can instantiate string objects.)

But this works as expected:
       
       int roo = 12;
       double daroo = 12.0;
       if (roo == daroo)
           System.out.println("Aroo! Aroo!");

(Confused and/or annoyed? A critic of many things Java has an entry titled "double Double Toil and Trouble." Check out his neat site for many other goodies.)

Let's see this working. We'll have a class called Car with private instance variables owner and color.  It has a two-argument constructor and two getters. (No setters needed in this case; the constructor gives values to the instance variables.) Here it is:

public class Car {
    private String owner;
    private String color;
	
    Car(String own, String c) {
        owner = own; 
        color = c;
    }
	
    public String getOwner() {
        return owner;
    }
	
    public String getColor() {
        return color;
    }
}
Now we have a class that instantiates cars:
public class MakeCars {

    public static void main(String[] args) {
        Car car1 = new Car("Dan", "blue");
        Car car2 = new Car("Dan", "blue");
        if (car1 == car2)
            System.out.println("What else?");
        else
            System.out.println("Who stole my car???");
    }
}

What do you think prints? How much would you be willing to bet on that? Here's the output:

    Who stole my car???

What happened? We created two blue cars, both owned by Dan. Why aren't they equal?  It's hard to conceal the problem, when I put it that way: they are in fact two cars. This becomes very clear if I add the line:

    System.out.println(car1 + " " + car2);

The output of this is:

    Car@82ba41 Car@923e30

When I say if (car1 == car2) I am asking if those two variables reference the same object. And of course they don't.

I can create a second reference to an object, like this:

    Car car3 = car1;
    if (car3 == car1)
        System.out.println("What else?");
    else
        System.out.println("Who stole my car???");
    System.out.println(car1 + "   " + car3);

The output this time is:

What else?
Car@82ba41 Car@82ba41

The basic, crucial, you-gotta-understand-it fact is, car3 is not a new car. It is a reference to car1. You can see that when we print the two variables as Objects.

This Won't Do, Will It? Overriding java.lang.equals()

There's got to be more to the story, right? Well, right. We need to be able to ask whether two objects are equivalent, based on instance variables they contain. Can do. Let's see how.

equals() is one of the handful of methods in the class Object, Object-with-a-Big-O, the class from which all other classes inherit. (That last statement is true whether or not your class explicitly extends anything.) Many classes override the equals() from Object; the wrapper classes do, for example.

But suppose we are comparing two objects instantiated from a class of our own creation, as we just did for cars. How can we specify that two cars are "equal" if and only if they have the same owner and the same color? The answer, of course, is to override the equals() from Object so that is does not just check the references, but looks at the values of owner and color. That isn't so hard, but there are issues. Here is our override from a class named NewCar, not shown since everything except this method is the same as in Car.

// This overrides java.lang.equals
    public boolean equals(Object o) {
        if ((o instanceof NewCar) 
            && (((NewCar) o).getOwner() == owner)
            && (((NewCar) o).getColor() == color))
            return true;
        else
            return false;
    }

In overview, if we say if (a.equals(b)) where a and b are Cars, we simply want to know if a and b have the same values of  owner and color. But of course it isn't quite that simple. Is anything?

Remember: equals() works only on two objects of the same class. You can use equals to compare a Double and an Integer, with no complaint from the compiler, but the result will always be false. So before we do any comparison we must make sure that both objects are instances of NewCar. (How could they not be? Well, in this case they couldn't, but let's follow the Best Practice and make the test.) If the (o instanceof NewCar) returns false, the && short circuits the rest of the boolean expression and the actual comparisons are not carried out. (And the result is false, of course, which is how the language works: equals()applied to two objects that are not instances of the same class always returns false.)

The next issue is that we start with an Object, but we want to consider it, in this case, as a NewCar. Polymorphism to the rescue: cast the Object to be a NewCar. Having done that, we are ready to apply the getOwner() method—but don't rush ahead too fast. Without the parentheses around ((NewCar) o), the getOwner() method would be applied before the cast, creating chaos.  And now we have tested whether the owners are the same and the colors are the same.

OK: who sees the error? Hint: the program compiles as shown, and gives correct results with the data shown--but it violates the whole point of the tutorial!!!

Give up? Look at this line:

(((NewCar) o).getOwner() == owner)

That's a comparison of two strings, . . . er, well, no, it's a comparison of references to two strings. Strings are objects, remember? (I said so, a few screens above.) If I want to compare the contents of two strings I have to use equals(). (See title of the tutorial.) Make that method like this:

//  This overrides java.lang.equals
    public boolean equals(Object o) {
        if ((o instanceof NewCar) 
            && (((NewCar) o).getOwner().equals(owner))
            && (((NewCar) o).getColor().equals(color)))
            return true;
        else
            return false;
    }

So why did I get by with ==? Because the Java Virtual Machine stores strings in a string pool, and whenever a string is created without using new(), it first checks to see if the new string already exists in the pool. If so, it simply provides a reference to the existing string. Two references to the same string means the references will test equal by ==.

There is no obvious and useful way to make this error happen in this example, that I can see, but we sure do like to get things right.

Huge thanks to Sergio Benavides, a computer engineering student at Florida International University, for pointing out this error.

Ahem. But Now We Have to Override hashCode() Too

Almost done. The thing is, the Java spec says that "If two objects are equal according to the equals method, they must return the same value from hashCode." Let's not press the "Why, Mommy?" right now. Time for that in the Collections tutorial.

There are approximately a zillion ways  to compute a hashcode, most of them either wrong or inefficient. You learned something about that in Data Structures; I don't know how many PhD theses have been written on the subject, but it must be into the hundreds. That's because it's important and because it's hard to do right. And it's used all the time. We can't do anything serious without collections (sets, lists, and maps), and the marvelous classes available since JDK 1.2 are completely dependent on hashcodes. (Well, most of them. LinkedLists and ArrayLists aren't.)

We are going to do something simple that may not  be too bad. We'll use the hashCode method of the String class to hash the owner's name and the color and return their sum as the hashcode for a NewCar. Here's the method, again from NewCar.


//  This overrides java.lang.hashCode
    public int hashCode() {
        return owner.hashCode() + color.hashCode();
    }


You can look up how the hashCode for Strings works in JDK 1.5 if you wish; there has apparently been a lot of work done improving it since the earliest Java versions. But as Java programmers trying to get a J2EE app running, we don't care. We trust that a whole bunch of very bright people have done it right, and get on with our work.


That's it for now, folks. Please mail me with error  reports and requests for clarification.

Back to Dan McCracken's  Home Page