انتقل إلى المحتوى
أمين أبوزيد
منشورBEG-026-L01

الفئة Object

  • الدرس 1 من 6
  • 10 دقيقة قراءة
  • مبتدئ
في هذا الدرس 11 موضوعات

هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.

Lesson Overview

You have written classes such as Account and SavingsAccount, and you have seen inheritance with extends. One question was left open: where does the top of every family tree sit? This lesson answers it. Every class in Java extends a class called Object, whether you write extends or not, so every object you create already has a small set of methods you never wrote.

Throughout this module we use a banknote picture. Two banknotes with the same serial number are the very same note (identity). Two different notes of the same value are equal money (equality). Java has to tell those two ideas apart, and the methods inherited from Object are where that happens.

Why this matters: when you print an object and see something like Account@4517d9a3, or when a collection treats two "identical" records as different, the cause is almost always a default method inherited from Object. Knowing where those defaults come from is the first step to fixing them.

This lesson builds on the inheritance and overriding lessons of the previous modules (BEG-022 and BEG-023). It needs no collections knowledge.

Learning Objectives

By the end of this lesson you will be able to:

  • State that every class extends Object, directly or indirectly, even without writing extends.
  • Name the methods every object inherits: toString, equals, hashCode, getClass, and the rarely used ones.
  • Say which three inherited methods are the ones you override most often.
  • Predict what the default toString and the default equals do.
  • Use an Object variable to hold any kind of object.
  • Explain why the defaults are "generic" and when you should replace them.

Written Explanation

Every class has a parent

In the inheritance module you wrote class SavingsAccount extends Account. What about Account itself? If you do not write extends, the compiler treats the class as if it said extends Object. So the family tree always ends at one root:

text
Object
  └── Account
        └── SavingsAccount

Java's own documentation says the same: Object is the root of the class hierarchy, and every class has it as a superclass. Because of inheritance, a SavingsAccount object has everything Account offers and everything Object offers.

That also means a variable of type Object can hold a reference to any object, because every object "is an" Object (the upcasting idea from BEG-023). This is why some general-purpose Java code is written in terms of Object.

The inherited methods

Here is what every object gets for free. The first four are the ones to know now.

MethodWhat it doesDefault behavior
toString()Returns text describing the objectClass name, @, then a number in hexadecimal
equals(Object other)Asks if another object is "equal" to this oneTrue only if it is the very same object
hashCode()Returns an int that summarizes the objectA number tied to the object's identity
getClass()Returns the object's real class at run timeCannot be overridden (it is final)
wait, notify, notifyAllCoordinate threadsTaught much later
clone()Makes a shallow copyprotected, rarely used by beginners

Two details are worth noticing. First, getClass() is final, so no class can change it: the answer is always the real class of the object. Second, the thread-related methods exist on every object for historical reasons; you will not use them in this module.

The defaults are generic

Because Object knows nothing about your class, its methods can only be generic:

  • The default toString() returns the class name, an @ sign, and the hash code in hexadecimal. The Java documentation notes the text is not guaranteed to be stable over time or between runs, so never rely on it.
  • The default equals is identity: a.equals(b) is true only when a and b are the same object, exactly like a == b.
  • The default hashCode() is tied to the object's identity too, and it is consistent with the default equals.

Think of the banknote: the default only recognizes "this exact note in my hand". It has no idea that two notes of 100 riyals are worth the same.

Which methods do you override?

Three of the inherited methods are overridden constantly:

  1. toString() so that printing and logging show something readable.
  2. equals(Object) so that two objects with the same value can count as equal.
  3. hashCode() which must always be overridden together with equals (Lessons 3 and 6).

You override them only when they make sense. An Account that is a unique living entity might keep identity equality. A Money value should have value equality. Lesson 2 gives the tools to decide.

Why have a single root at all?

A common root gives the language one guaranteed minimum that every object supports. Library authors can write code such as "store this thing, print this thing, compare this thing" without knowing your class, because they can count on Object's methods being there. It also means the compiler can always answer "does this call make sense?" for the basic methods. The price is that the defaults must be cautious: they cannot assume anything about your fields.

A good habit when you meet a class for the first time is to ask three questions: what does it print as, what does it consider equal, and what hash does it give? If the answers are the Object defaults, then printing shows Name@hex, equality is identity, and the hash follows identity. That is perfectly acceptable for some classes and a hidden bug for others.

Seeing the defaults

java
class Account {
    private final String owner;
    private final String number;
    Account(String owner, String number) { this.owner = owner; this.number = number; }
}

Account a = new Account("Ana", "A-1");
System.out.println(a);                  // Account@4517d9a3
System.out.println(a.getClass().getName());  // Account

println(a) calls toString() for you. The class has no toString of its own, so the one from Object runs. The number after @ will be different on your machine and may change between runs.

Examples

Example 1: the default toString (simple)

java
Account a = new Account("Ana", "A-1");
System.out.println(a);
System.out.println(a.getClass().getName());
System.out.println(a.toString().equals(
        a.getClass().getName() + "@" + Integer.toHexString(a.hashCode())));

Real output from one run (the hexadecimal number will differ on your machine):

text
Account@4517d9a3
Account
true

The third line confirms the rule: the default text is exactly class name, @, and the hash code in hexadecimal.

Example 2: methods on a class that declares none (practical)

java
class Point { }

Point p = new Point();
Point q = new Point();
System.out.println(p.equals(p));
System.out.println(p.equals(q));
text
true
false

Point is completely empty, yet p.equals(q) compiles. The method comes from Object. Its answer is false because p and q are two different objects; the default equals has no way to compare fields it knows nothing about.

Example 3: one variable type for everything (realistic)

java
Object[] things = { "hello", new SavingsAccount("Ben", "S-7"), new Point() };
for (Object o : things) {
    System.out.println(o.getClass().getSimpleName());
}
text
String
SavingsAccount
Point

The array holds three unrelated kinds of objects because each one is an Object. Through an Object reference we can only call Object's methods (getClass, toString, equals, hashCode). That is exactly why logging frameworks, collections, and many library methods can accept "anything" and still call toString() or equals on it. getSimpleName() returns the class name without its package.

Backend Insight

Backend code constantly handles objects it did not create: a request, a database row mapped into an object, an item stored in a collection. Libraries cannot know your classes, so they rely on the one contract all classes share: the Object methods.

  • A logger that prints your object calls toString(). If you did not override it, your log shows OrderItem@7a81197d, which is useless at 3 a.m. during an incident.
  • Collections that find, remove, or de-duplicate items call equals and hashCode. If you did not decide what "equal" means for your class, they use identity, which is often not what the business wants.
  • Frameworks that map data to objects (you will meet them later) assume you follow these contracts.

So the habit is: for every class that represents data, decide deliberately whether the three defaults are right.

Common Mistakes

Mistake 1: Believing you must write extends Object

You never need to. It is added automatically. Writing it is legal but pointless.

Mistake 2: Trusting the default toString text

  • Incorrect: parsing Account@4517d9a3 or storing it as an id.
  • Correct: treat it as a debugging hint at best. It is not stable and not unique by contract.

Mistake 3: Expecting equals to compare fields automatically

  • Incorrect: assuming two Account objects with the same owner and number are equal.
  • Correct: the default compares identity. If you want value equality, you must override equals (and hashCode).

Mistake 4: Calling a subclass method through an Object variable

Object o = new SavingsAccount(...); o.monthEnd(); does not compile, because the declared type Object has no monthEnd. As you learned in the polymorphism module, the compiler checks the declared type.

Mistake 5: Forgetting that subclasses inherit the parent's overrides

If Account overrides toString and SavingsAccount extends Account does not, a savings account prints with the Account text. That is inheritance working, not a bug, but the output will not mention the subclass's own fields unless you override again.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. Which class does class Invoice { } extend if you write no extends?

أظهر الحل A1

A1. Object.

A2. Name four methods every object inherits from Object.

أظهر الحل A2

A2. Any four of toString, equals, hashCode, getClass, clone, wait, notify, notifyAll.

A3. What does the default equals compare?

أظهر الحل A3

A3. The default (from Object) compares identity: it is true only when both references point to the very same object, like ==.

Level B: Application

B1. class Ticket { } has no methods. Is new Ticket().toString() legal? What does the result look like?

أظهر الحل B1

B1. Yes, it is legal because Ticket inherits toString from Object. The result looks like Ticket@ followed by a hexadecimal number, for example Ticket@1b6d3586 (the number varies).

B2. You write Object o = "text"; then o.length();. Why does the second line fail to compile, although the object really is a String?

أظهر الحل B2

B2. The compiler checks the declared type, which is Object, and Object has no length() method. Only the String type has it. You would need a cast to String, which is only safe after an instanceof check.

B3. Why is the default equals the right choice for a class such as DatabaseConnection, but a poor choice for Money?

أظهر الحل B3

B3. A database connection is a unique resource: two connections are different even if they point to the same database, so identity is the correct meaning of "equal". Money is a value: 100 SAR equals any other 100 SAR, so equality should compare the fields.

Level C: Challenge

C1. Write a loop over Object[] items that prints, for each item, its class's simple name followed by its toString() result, separated by a colon and a space. Explain why it works for items of any class.

أظهر الحل C1

C1.

java
for (Object item : items) {
    System.out.println(item.getClass().getSimpleName() + ": " + item.toString());
}

It works for any class because every object inherits getClass() and toString() from Object, and toString() runs the overriding version of the real class when there is one (dynamic dispatch).

C2. a.toString() returns Account@4517d9a3. Explain why you cannot use that text to tell whether two accounts are the same account in your database.

أظهر الحل C2

C2. The default text is built from the hash code, which is not guaranteed to be unique, may change between runs, and says nothing about your data. An account should be identified by a real identifier such as its account number, stored in a field.

Mini Challenge

Create three small classes: Book, Member, and Loan, each with two private fields and a constructor. Without overriding anything, create one object of each, print it, and print a.equals(b) for two objects of the same class made with identical data. Write two sentences stating what the output shows about the defaults.

Check your work: every printout is ClassName@hex, the equals result is false, and your sentences say the defaults are based on identity.

Summary & Next Lesson

Key takeaways

  • Every class extends Object, directly or indirectly; the compiler adds it when you write no extends.
  • Every object has toString, equals, hashCode, and getClass.
  • The defaults are generic: a cryptic toString, identity-based equals, and an identity-based hashCode.
  • toString, equals, and hashCode are the three methods you will override most often.
  • An Object variable can refer to any object, but only Object's methods can be called through it.

Next lesson

The next lesson, "Identity vs equality", separates == from equals, and explains why comparing strings with == is a classic bug.

Self-check

  1. What is the root class of every Java class?
  2. Is getClass() overridable?
  3. What does the default toString print?
  4. Which three methods are most often overridden?
  5. Why can you not call monthEnd() on an Object variable?
أظهر الإجابات
  1. Object.
  2. No, it is final.
  3. The class name, an @, and the hash code in hexadecimal.
  4. toString, equals, and hashCode.
  5. The declared type Object has no such method; the compiler checks the declared type.

References

  • Oracle, Java SE 21 API: java.lang.Object. It states that Object is the root of the class hierarchy, lists the methods (clone, equals, getClass, hashCode, notify, toString, wait), gives the default toString format and warns it is not stable, and says the default equals is identity. The banknote analogy is this course's own.
  • The Account@4517d9a3 output above was produced by compiling and running the examples on JDK 21; the number differs per run.

التطبيق

طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.

المختبر والتمارين الاختبار

تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.

اكتب للبحث في المستويات والمراحل والوحدات والدروس.