Concepts / The Object Class

The Object Class

The default equals(obj) inherited from class Object tests whether two references point to the same object, which gives the same result as ==.

  • Programming

Two Assumptions Beginners Bring to Objects

Many programmers arriving at Java hold 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?

  • true

  • false

Reveal answer
Answer:

false

The default equals() inherited from Object tests whether two references point to the same object. These are two separate objects, so the default test returns false even though their stored numbers match.

What the Default equals() Actually Tests

Definition

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. Its 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 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 that the class itself has chosen to answer. With the default implementation, the two forms agree. Once a class overrides equals() to compare contents, the two forms can disagree, and the result of obj1.equals(obj2) depends on what that particular class decided equality means.

dice1reference to an objectPairOfDice object Adie1 = 3, die2 = 6dice2reference to an objectPairOfDice object Bdie1 = 3, die2 = 6dice1reference to an objectPairOfDice object Cdie1 = 3, die2 = 6dice2reference to the sameobjectrefers torefers torefers torefers to
When two variables refer to separate objects with identical field values, why does the default equals() return false, while two variables referring to the same object return true?

The diagram shows the first case in the top group. Each variable refers to its own object, so the default equals() returns false, even though both objects store a 3 and a 6. In the bottom group, both variables refer to one object. The default equals() returns true, because the two references name the same object. The default test never looks at the numbers inside the objects at all.

Overriding equals() to Compare Contents

The source explains that 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 example below is an original illustration of the same idea for a small Card class. Its equals() method checks that the argument is a Card, then compares the fields that define a card.

Code walkthrough
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);
    }
}
  1. 1
    The class declares two instance variables that define a card.
  2. 2
    The constructor stores the values passed in.
  3. 3
    The equals method keeps the same parameter type as the inherited version, so Java uses this version when equals() is called.
  4. 4
    If the argument is not a Card, the method returns false immediately.
  5. 5
    Otherwise the method compares the rank values and the suit strings and returns true only when both match.
  • The signature public boolean equals(Object obj) matches the inherited method exactly.
  • The instanceof check rejects arguments that are not Cards.
  • The cast gives access to the other card's fields.
  • Two cards are equal when their rank and suit match, regardless of which objects they are.
Before
Card a = new Card("Hearts", 7);
Card b = new Card("Hearts", 7);
System.out.println(a.equals(b));

Class Card does not override equals(), so the inherited default compares references. The two cards are separate objects, so the result is false.

After
Card a = new Card("Hearts", 7);
Card b = new Card("Hearts", 7);
System.out.println(a.equals(b));

Class Card overrides equals() to compare contents. The two cards have the same suit and rank, so the result is true.

Mistakes When Writing equals()

  • Common mistake

    Writing equals() with a parameter type other than Object

    What it looks like
    public boolean equals(Card other) { ... }
    Why it fails

    This is a different method from the inherited equals(Object obj). Java still calls the default version when you compare objects, so your logic is silently ignored.

    Do this instead

    Use the exact signature public boolean equals(Object obj), then cast the argument after checking its type.

  • Common mistake

    Assuming == and equals() always agree

    Why it fails

    With the default implementation they agree, but once a class overrides equals() to compare contents, obj1.equals(obj2) can return true while obj1 == obj2 returns false.

    Do this instead

    Check whether the class overrides equals() before deciding how two objects will compare.

The Default toString() Output

Definition

The instance method toString() in class Object returns a value of type String that is supposed to represent the object as text. The default version returns the name of the class the object belongs to, joined with a code number called the object's hash code.

The source describes this default as not very useful. It tells you the kind of object you have, but not what the object contains. A printed line that shows only a class name and a number therefore tells a programmer that Java used the inherited method, and nothing about the values stored inside. When you create a class, you can write a new toString() method, which replaces the inherited version. The replacement is what Java will use from then on whenever it needs a string form of your objects.

Writing a toString() for PairOfDice

The source gives a toString() method for the PairOfDice class. 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.

Code walkthrough
public String toString() {
    if (die1 == die2) {
        return "double " + die1;
    }
    else {
        return die1 + " and " + die2;
    }
}
  1. 1
    The method compares the two dice values.
  2. 2
    If they are equal, it builds a string starting with the word double followed by the value.
  3. 3
    Otherwise it joins the two values with the word and between them.
  4. 4
    The returned string describes what the object actually holds, which the default version never did.
  • The method name and return type match the inherited toString() in Object.
  • The string describes the state of the object rather than only its class.
Output
3 and 6   (when die1 is 3 and die2 is 6)
double 2  (when die1 and die2 are both 2)
Worked example

Tracing toString() for Two Different Rolls

A PairOfDice object has die1 equal to 5 and die2 equal to 1. Then a second object has die1 equal to 2 and die2 equal to 2. What string does toString() return for each?

  1. 1
    Check the first object

    die1 is 5 and die2 is 1. The values are not equal, so the else branch runs.

  2. 2
    Build the first string

    The method returns die1 + " and " + die2, which is 5 and 1.

  3. 3
    Check the second object

    die1 is 2 and die2 is 2. The values are equal, so the if branch runs.

  4. 4
    Build the second string

    The method returns "double " + die1, which is double 2.

Result

The first object gives 5 and 1, and the second gives double 2.

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 conversion is automatic, which is why a missing toString() override tends to show up in printed output rather than as an error. If you print an object and see a class name followed by a code number, Java has used the inherited default version, and that is a signal to check whether the class defines its own toString().

Program codeSystem.out.println(dice)Print stepneeds a StringPairOfDice objecttoString() method1. print the object2. convert to String3. returns "3 and 6"4. text is displayed
When is toString() called without the programmer calling it directly, such as in System.out.println or string concatenation, and in what order do these steps happen?

The sequence above follows the source's example of a PairOfDice object inside a print statement. The program asks for the object to be printed. Because printing needs text, Java calls the object's toString() method and uses the string it returns. Concatenation follows the same pattern: when an object is joined onto a string with the plus operator, Java first obtains its string form from toString().

Object at the Root of Every Class Chain

Most of what you have learned about object-oriented programming so far centers on subclasses. A subclass takes on the properties and behaviors of an existing class, and it can change those inherited behaviors or add new ones of its own. This section looks at a side of the same system you may not have noticed yet: every class in Java, with one exception, already sits underneath some other class. The class that makes this possible is named Object. Object is a special class in package java.lang and is the one class in Java that is not a subclass of any other class.

Think of Object as the root of the whole class structure. Every chain of parents, followed upward from any class you write, eventually reaches Object. The chain can be one step long, as with a class that has no explicit parent, or several steps long, as with a class that inherits from a class that itself inherits from another. In every case, the top of the chain is Object.

Objectroot of the hierarchyAnimalno extends clauseDogextends AnimalCarno extends clausesuperclasssuperclasssuperclass
If a class has no extends clause, what is its superclass, and how does every class's superclass chain eventually reach Object?

The diagram shows a small hierarchy. Animal has no extends clause, so it sits directly under Object. Dog is written to extend Animal, so it sits one level below Animal. Car has no extends clause either, so it sits directly under Object as well. Follow any arrow upward and you reach Object. The source does not walk through this exact example, but it follows directly from its statements that every class except Object is a subclass of some other class and that a class with no explicit parent becomes a subclass of Object.

Finding the Superclass of a Class

Worked example

Determining the Superclass of Three Classes

Classes Animal, Dog, and Car are declared as follows: Animal has no extends clause, Dog extends Animal, and Car has no extends clause. What is the superclass of each class?

  1. 1
    Check Animal

    Animal has no extends clause, so it automatically becomes a subclass of Object. Its superclass is Object.

  2. 2
    Check Dog

    Dog states extends Animal, so its direct superclass is Animal. Animal's superclass is Object, so Dog's chain is Dog, Animal, Object.

  3. 3
    Check Car

    Car has no extends clause, so its superclass is Object, the same as Animal.

Result

Animal and Car have Object as their superclass. Dog has Animal as its direct superclass, and every chain ends at Object.

java
Notes on the code
  • Animal has no extends clause, so its superclass is Object.
  • Dog extends Animal, so its superclass is Animal.
  • Car has no extends clause, so its superclass is Object.

Why Every Object Gets Object's Methods

Because every class is a subclass of Object, a variable whose type is Object can refer to an object of any class you write. The second major fact in this section concerns behavior rather than structure. Class Object defines several instance methods, and because every other class is a subclass of Object, every other class inherits those methods. The source says these methods can be used with any object whatsoever. You do not need to write any special code in a class for its objects to have access to the methods defined in Object.

java
Notes on the code
  • Each variable has type Object but refers to a different class's object.
  • equals() and toString() are called on each object without any code in Dog, Car, or PairOfDice declaring them directly.
  • Each call uses whichever version the object's class has provided, either its own override or the inherited default.

Tracing equals() and toString() Through Code

MEDIUM

A class Card overrides equals() to compare suit and rank, as shown earlier. Two variables, x and y, are declared as new Card("Spades", 4) and new Card("Spades", 4). A third variable, z, refers to the same object as x. Predict the result of x.equals(y), x == y, and x.equals(z). Then say whether printing x would show the card's contents if Card does not override toString().

Hints
  • x and y are separate objects, so == compares references and returns false.
  • The overridden equals() compares rank and suit, so x.equals(y) returns true.
  • x and z refer to the same object, so x == z is true, and equals() also returns true.
  • Without an override, printing x shows only the class name and hash code, not the suit or rank.

Key Takeaways

  • The default equals(obj) inherited from class Object tests whether two references point to the same object, so it gives the same result as ==. Two separate objects with identical contents are not equal under the default.
  • A class overrides equals() using the exact signature public boolean equals(Object obj) to define equality by contents, as the String class does for character sequences.
  • The default toString() returns the class name and hash code, which rarely describes the object's state, so classes usually write their own toString() method.
  • Java calls toString() automatically when an object is used in a string context, such as printing or concatenation.
  • Object is the one class that is not a subclass of any other class. Every class without an explicit parent becomes a subclass of Object, so every superclass chain ends at Object, and every object can use Object's methods.