Writing Classes and Instance Variables
The default equals(obj) inherited from class Object tests whether two references point to the same object, which gives the same result as ==.
Two Beliefs About Objects
Many programmers arrive at Java with two working assumptions about objects. The first is that if two objects hold the same data, a test for equality will report that they match. The second is that printing an object will display its useful contents. Both assumptions feel reasonable, and both are wrong for the default behavior Java provides. This article takes each belief in turn, shows what Java actually does, and then shows how a class can change that behavior by overriding two methods it inherits from class Object.
What do you think happens?
Suppose you have a class called PairOfDice, and you create two separate PairOfDice objects, each showing a 3 and a 6. Each object stores its own pair of numbers. What do you think a call to equals() between the two objects will return?
Reveal answer
false
The default equals() inherited from Object tests whether two references point to the same object. Two separate objects are never the same object, so the result is false even though their stored numbers match. The next sections explain why.
Two Objects, Two References
Each PairOfDice object stores its own pair of numbers, and a variable holding an object actually holds a reference that names that object. Two variables can name two different objects that happen to contain identical values. The diagram below shows that situation for the dice from the opening question.
The two references are different, so the default test reports that the objects do not match, even though the numbers inside them agree. The default equals() is not looking at the dice at all. It is looking at which object each reference names.
The Default equals Method
The default equals(obj) inherited from class Object tests whether two references point to the same object. It gives the same result as the == operator.
The method signature is public boolean equals(Object obj). Because every class inherits this method from Object, every object has an equals() method available, even if the class never mentions it. The source also notes that what it means for two objects to be equal is not always clear, and that this question returns later in the course.
The two forms of comparison answer different questions. The expression == asks whether two references name the same object. The expression obj1.equals(obj2) asks the question the class itself has chosen to answer. With the default implementation the two forms agree. Once a class overrides equals() to compare contents, they can disagree, and the result of obj1.equals(obj2) depends on what that class decided equality means.
If you have not overridden equals(), then a call to equals() on your class behaves exactly like ==. Two objects with identical contents will compare as unequal.
Overriding equals to Compare Contents
It is common for a class to override equals() so that two of its objects count as equal when they have the same contents. The String class does this: two Strings are equal when they contain the same sequence of characters. The following original example, written for a small Card class, shows the same idea. Its equals() method checks that the argument is a Card, then compares the fields that define a card.
public class Card {
private String suit;
private int rank;
public Card(String suit, int rank) {
this.suit = suit;
this.rank = rank;
}
public boolean equals(Object obj) {
if (!(obj instanceof Card)) {
return false;
}
Card other = (Card) obj;
return this.rank == other.rank && this.suit.equals(other.suit);
}
}- 1The constructor stores the suit and rank that each Card object will carry.
- 2
equals()receives any Object as its argument, so it first confirms the argument is a Card. - 3The cast lets the method read the rank and suit fields of the other Card.
- 4The return statement reports true only when both the rank and the suit match, so two different Card objects with the same contents compare as equal.
- The instance variables suit and rank hold the contents that define each card.
- The overriding
equals()keeps the inherited signature, public booleanequals(Object obj). - The instanceof check rejects arguments that are not Cards.
- The final line compares the two fields rather than the references.
Notice that the overriding method keeps the same signature as the inherited one. Java decides which version runs based on that signature. The rule is simple to state and easy to break, so the next section covers the most common way to get it wrong.
Mistakes When Overriding equals
A programmer writes equals(Card other) instead of equals(Object obj) and then reports that two Card objects with matching contents still compare as unequal.
Common mistake Changing the parameter type when overriding equals()
What it looks likepublic boolean equals(Card other) { ... }Why it failsThe method no longer has the signature public boolean
equals(Object obj). Java does not treat it as an override, so the default version from Object still runs whenequals()is called.Do this insteadKeep the parameter as Object and cast inside the method after checking the type with instanceof.
Common mistake Comparing references inside an overridden equals()
What it looks likereturn this == other;Why it failsThis repeats the default behavior. Two separate objects with identical contents still return false.
Do this insteadCompare the instance variables that define the object's contents, as the Card example does.
Common mistake Forgetting that an overridden equals() is silently ignored if the signature is wrong, so the test appears to run while the new logic never executes
The Default toString Method
The instance method toString() in class Object returns a String intended to represent the object as text. The default version returns the name of the object's class joined with a code number called the object's hash code. The source describes this default as not very useful. It tells you what kind of object you have, but not what the object contains.
If you print a PairOfDice object and the output is a class name followed by a number, you learn that the object exists and what class it belongs to. You do not learn that it shows a 3 and a 6. The default output says nothing about the state held in the instance variables.
When you create a class, you can write a new toString() method that replaces the inherited version. From then on, Java uses the replacement whenever it needs a string form of your objects.
Writing a Meaningful toString for PairOfDice
The source gives a toString() method for PairOfDice. It uses the two instance variables die1 and die2, which hold the numbers showing on the dice. When the two numbers are equal, the method reports a double. Otherwise it reports the two numbers separated by the word and.
For dice showing 3 and 6: 3 and 6
For dice showing 2 and 2: double 2- The method replaces the inherited
toString()because it has the same signature. - The string describes the state held in die1 and die2, which the default version never did.
The method name is not unusual. What matters is that the returned string now describes the object's state. A printed PairOfDice object shows the actual numbers on its dice rather than a class name and a code number.
When Java Calls toString Automatically
You have probably already used toString() without calling it yourself. When you use an object in a context that requires a string, Java converts the object to type String by calling its toString() method. Printing an object and concatenating an object onto a string are the two common cases. The sequence below traces a print statement that uses a PairOfDice object.
The conversion is automatic, which is why a missing toString() override usually shows up in printed output rather than as a compiler error. If you print an object and see a class name followed by a code number, Java used the inherited default. That is a signal to check whether the class defines its own toString().
When a program prints something unexpected, ask which method produced the result. A printed line that looks like a class name with a number points to the default toString(). A false answer from comparing two objects with matching contents points to the default equals(), which is still comparing references. Check whether the class actually overrides the method you expected it to override.
Practice: Diagnosing Which Method Ran
A class Coin stores a single instance variable, side, holding the string heads or tails. Two Coin objects both have side set to heads. Without overriding anything, predict what equals() returns for the two objects, and predict what printing one of them shows. Then write a short equals() and toString() for Coin that would make both results meaningful.
Hints
- Recall that the default
equals()compares references, so two separate objects will not match even if their side is the same. - Keep the signature public boolean
equals(Object obj)and compare the side field inside the method. - A
toString()that returns the side value tells you the state, which the default output never did.
Summary of Equality and String Forms
- 1The default
equals(obj)from class Object tests whether two references point to the same object, giving the same result as ==, so two separate objects with identical contents compare as unequal. - 2Overriding
equals()with the signature public booleanequals(Object obj)lets a class define equality by its contents, as the String class does for character sequences. - 3The default
toString()returns the class name and hash code, which says little about an object's state, so classes usually override it. - 4Java calls
toString()automatically when an object is used in a string context, such as concatenation or printing, and the returned String is what gets displayed.
At a glance
- The default
equals()compares references, so separate objects with identical contents are not equal under it. - Override
equals()with the signature public booleanequals(Object obj)and compare the fields that define the object's contents. - The default
toString()shows only the class name and hash code, so override it to describe the object's state. - Java calls
toString()automatically during concatenation and printing, so a missing override often appears in output.