The Object Class in Java

Every class inherits from Object, which is where toString, equals, hashCode and getClass come from.

The root of every hierarchy

java.lang.Object is the superclass of every class in Java. A class that declares no extends clause extends Object implicitly, and a class that extends something else still reaches Object further up the chain.

class Note { }              // implicitly extends Object

Object anything = new Note();      // always valid
Object number = 42;                // autoboxed to Integer, still an Object
Object array = new int[3];         // arrays are objects too

This is why a List<Object> can hold anything, and why the collections framework could be written before generics existed.

The methods it provides

MethodPurposeUsually overridden
toString()A readable descriptionYes
equals(Object)Logical equalityYes, for value types
hashCode()A hash bucket for collectionsAlways with equals
getClass()The runtime classNo, it is final
clone()A field by field copyRarely, and reluctantly
wait(), notify(), notifyAll()Low level thread coordinationNo, they are final

toString

class Note {

    private final long id;
    private final String title;

    Note(long id, String title) {
        this.id = id;
        this.title = title;
    }

    @Override
    public String toString() {
        return "Note[id=" + id + ", title=" + title + "]";
    }
}
Note note = new Note(7, "Java basics");
System.out.println(note);          // Note[id=7, title=Java basics]
System.out.println("Saved " + note);   // toString is called implicitly

The inherited version prints the class name and an unsigned hexadecimal hash, which tells a reader almost nothing. Overriding toString is the cheapest improvement you can make to a class, because it pays off in every log line and every debugger session.

getClass

Object value = "text";

System.out.println(value.getClass().getName());        // java.lang.String
System.out.println(value.getClass().getSimpleName());  // String
System.out.println(value.getClass() == String.class);  // true

getClass returns the runtime class, not the declared type. It is final, so no class can lie about what it is.

equals and hashCode, in brief

Note a = new Note(7, "Java basics");
Note b = new Note(7, "Java basics");

System.out.println(a.equals(b));   // false, unless equals is overridden

The inherited equals compares references, so two objects are equal only if they are the same object. For value types that is rarely what you want. These two methods have a contract that must be respected together, covered in detail in the note on equals and hashCode.

clone and why it is avoided

class Settings implements Cloneable {

    private final List<String> roles = new ArrayList<>();

    @Override
    protected Settings clone() throws CloneNotSupportedException {
        Settings copy = (Settings) super.clone();   // shallow: roles is shared
        return copy;
    }
}

Object.clone makes a shallow copy: every field is copied bit for bit, so referenced objects are shared rather than duplicated. The mechanism is awkward, requires the marker interface Cloneable, and throws a checked exception. A copy constructor or a static factory is clearer.

class Settings {

    private final List<String> roles;

    Settings(Settings other) {                 // a copy constructor
        this.roles = new ArrayList<>(other.roles);
    }
}

The threading methods

wait, notify and notifyAll exist on Object because every object can act as a monitor lock. They must be called while holding that object monitor, and modern code should prefer the utilities in java.util.concurrent instead.

Common mistakes

  • Printing an object and getting output such as Note@1b6d3586, then wondering what went wrong. Nothing did; toString was simply not overridden.
  • Overriding equals and forgetting hashCode.
  • Writing public boolean equals(Note other), which overloads rather than overrides. The parameter must be Object.
  • Using clone and assuming the copy is independent.
  • Comparing types with getClass().getName().equals("...") instead of instanceof.

Best practices

  • Override toString on every class that will appear in a log or an error message.
  • Override equals and hashCode together, or use a record and let the compiler do it.
  • Prefer a copy constructor over clone.
  • Keep sensitive values such as passwords out of toString.

Practice

  1. Print an object without overriding toString, then add one and compare the output.
  2. Why is getClass() declared final?
  3. Explain the defect in public boolean equals(Note other) and how @Override would have caught it.
  4. Write a copy constructor for a class holding a mutable list, and show that changing the copy leaves the original alone.
  5. What does new int[3].getClass().getSimpleName() return?

Conclusion

Everything in Java is an Object, which is why every reference offers toString, equals, hashCode and getClass. Override the first three thoughtfully, and prefer copy constructors to clone.

Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All Java notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.