الفئة Object
في هذا الدرس 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 writingextends. - 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
toStringand the defaultequalsdo. - Use an
Objectvariable 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:
Object
└── Account
└── SavingsAccountJava'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.
| Method | What it does | Default behavior |
|---|---|---|
toString() | Returns text describing the object | Class name, @, then a number in hexadecimal |
equals(Object other) | Asks if another object is "equal" to this one | True only if it is the very same object |
hashCode() | Returns an int that summarizes the object | A number tied to the object's identity |
getClass() | Returns the object's real class at run time | Cannot be overridden (it is final) |
wait, notify, notifyAll | Coordinate threads | Taught much later |
clone() | Makes a shallow copy | protected, 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
equalsis identity:a.equals(b)is true only whenaandbare the same object, exactly likea == b. - The default
hashCode()is tied to the object's identity too, and it is consistent with the defaultequals.
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:
toString()so that printing and logging show something readable.equals(Object)so that two objects with the same value can count as equal.hashCode()which must always be overridden together withequals(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
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()); // Accountprintln(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)
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):
Account@4517d9a3
Account
trueThe 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)
class Point { }
Point p = new Point();
Point q = new Point();
System.out.println(p.equals(p));
System.out.println(p.equals(q));true
falsePoint 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)
Object[] things = { "hello", new SavingsAccount("Ben", "S-7"), new Point() };
for (Object o : things) {
System.out.println(o.getClass().getSimpleName());
}String
SavingsAccount
PointThe 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 showsOrderItem@7a81197d, which is useless at 3 a.m. during an incident. - Collections that find, remove, or de-duplicate items call
equalsandhashCode. 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@4517d9a3or 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
Accountobjects with the same owner and number are equal. - Correct: the default compares identity. If you want value equality, you must override
equals(andhashCode).
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.
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 noextends. - Every object has
toString,equals,hashCode, andgetClass. - The defaults are generic: a cryptic
toString, identity-basedequals, and an identity-basedhashCode. toString,equals, andhashCodeare the three methods you will override most often.- An
Objectvariable can refer to any object, but onlyObject'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
- What is the root class of every Java class?
- Is
getClass()overridable? - What does the default
toStringprint? - Which three methods are most often overridden?
- Why can you not call
monthEnd()on anObjectvariable?
أظهر الإجابات
Object.- No, it is
final. - The class name, an
@, and the hash code in hexadecimal. toString,equals, andhashCode.- The declared type
Objecthas no such method; the compiler checks the declared type.
References
- Oracle, Java SE 21 API:
java.lang.Object. It states thatObjectis the root of the class hierarchy, lists the methods (clone,equals,getClass,hashCode,notify,toString,wait), gives the defaulttoStringformat and warns it is not stable, and says the defaultequalsis identity. The banknote analogy is this course's own. - The
Account@4517d9a3output above was produced by compiling and running the examples on JDK 21; the number differs per run.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.