إعادة تعريف الدوال (Overriding)
في هذا الدرس 11 موضوعات
هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.
Lesson Overview
So far a subclass has only added things to its parent. Often that is not enough. A checking account lets the balance go below zero up to an overdraft limit, but the parent Account.withdraw forbids that. The child needs the same method name, with different behavior.
This lesson introduces overriding: writing a method in the subclass with the same name and parameters as one in the parent, so objects of the subclass use the new body. In the family tree picture, a child inherits the family recipe but cooks it its own way.
Why this matters: overriding is how you customize behavior in framework classes, and a single missing annotation, @Override, is behind many silent bugs. You will learn to type it every time.
This lesson builds on "Constructors and super" and uses super.method() from that lesson.
Learning Objectives
By the end of this lesson you will be able to:
- Override an inherited instance method and tell overriding from adding a new method.
- Use
@Overrideand explain what it protects you from. - Call the parent's version from the override with
super.method(). - State the rules an override must follow (name, parameters, access, return type).
- Explain why
privatemethods and constructors cannot be overridden, and what happens withstaticmethods. - Keep an override within the parent's promises.
Written Explanation
What overriding is
A subclass overrides an instance method when it declares a method with the same name, the same number and types of parameters, and the same return type as a method it inherited. The Oracle tutorial puts it this way: such a method in the subclass overrides the superclass method, and the subclass version is used for objects of the subclass.
class Animal {
String speak() { return "..."; }
}
class Dog extends Animal {
@Override
String speak() { return "Woof"; }
}A Dog object answers speak() with Woof; an Animal object still answers .... The parent is not changed.
Overriding is not adding
Change the name or the parameters and you have not overridden anything: you added a new method next to the inherited one. The tutorial notes that a method with the same name but a different parameter list is an overload, which neither hides nor overrides the original.
This is where the trap sits. Suppose you meant to override speak() but typed speek():
class Dog extends Animal {
String speek() { return "Woof"; }
}This compiles without complaint. new Dog().speak() prints ..., the parent's answer. The program is wrong, and nobody told you.
@Override: let the compiler check
Add the annotation and the compiler verifies your intention:
class Cat extends Animal {
@Override
String speek() { return "Meow"; }
}Main.java:8: error: method does not override or implement a method from a supertype
@Override
^
1 errorThe tutorial says that @Override tells the compiler you intend to override a method, and that the compiler reports an error if no such method exists in a superclass. It costs ten characters and removes a whole class of silent bugs. It also tells the next reader: "this replaces something inherited." Make it a habit, as automatic as a semicolon.
Rules for an override
| Rule | Detail |
|---|---|
| Same signature | Same name and same parameter types, in the same order |
| Return type | Same type (a more specific subtype is also allowed; you will rarely need it) |
| Access | The same or wider, never narrower |
| Instance only | An instance method cannot be overridden by a static method, or the other way round |
The access rule is easy to break. If the parent's method is public and you write the override without a modifier, you narrowed it:
Main.java:7: error: run() in C cannot override run() in P
void run() { }
^
attempting to assign weaker access privileges; was publicWhy the rule exists: anyone allowed to call the parent's method must also be allowed to call the child's version.
Calling the parent's version
Use super.method() inside an override to run the parent's code and then add to it, instead of repeating it:
class Tool {
String name() { return "tool"; }
String use() { return "using " + name(); }
}
class Hammer extends Tool {
@Override
String name() { return "hammer"; }
@Override
String use() { return super.use() + "!"; }
}new Hammer().use() prints using hammer!. Two things happen. super.use() runs the code in Tool.use(). Inside that code the call name() is made on the current object, which is a Hammer, so the hammer's name() runs. That "the real object decides" behavior is the heart of the next module; for now just notice it.
What cannot be overridden
- Constructors are not inherited, so there is nothing to override.
privatemethods are not inherited and a child cannot see them. If a child declares a method with the same name, it is a separate, unrelated method; adding@Overrideto it gives the "does not override" error.staticmethods belong to the class, not to objects. A subclass static method with the same signature hides the parent's; it is not an override. The tutorial's example shows that which one runs depends on the class used in the call, not the object. Rule of thumb: do not declare a static method in a subclass with the same signature as a parent's static method.finalmethods cannot be overridden. That is the next lesson.
Do not weaken the promise
An override must still keep the promises the parent made. The parent's withdraw promises: "the balance never goes below zero." If a child's override breaks that promise silently, code written against Account can break.
A legitimate change of rules must be part of the design. A checking account with an overdraft is a different kind of account with an allowed negative balance, which the bank explicitly decided. A savings account that charges a fee and accidentally pushes the balance below zero is a bug. Example 3 shows that bug.
Examples
Example 1: a typo that @Override catches (simple)
The Animal, Dog, Cat code above shows it. Without @Override, the Dog with speek() compiles and silently prints ... for speak(). With @Override on the Cat, the compiler stops you with method does not override or implement a method from a supertype.
Example 2: CheckingAccount with an overdraft (practical)
class Account {
private final String owner;
private double balance;
Account(String owner, double balance) {
this.owner = owner;
this.balance = balance;
}
String getOwner() { return owner; }
double getBalance() { return balance; }
void deposit(double amount) {
if (amount <= 0) {
System.out.println("Deposit refused");
return;
}
balance += amount;
}
boolean withdraw(double amount) {
if (amount <= 0 || amount > balance) {
System.out.println("Withdrawal refused");
return false;
}
balance -= amount;
return true;
}
protected void changeBalance(double delta) {
balance += delta;
}
}
class CheckingAccount extends Account {
private final double overdraftLimit;
CheckingAccount(String owner, double balance, double overdraftLimit) {
super(owner, balance);
this.overdraftLimit = overdraftLimit;
}
@Override
boolean withdraw(double amount) {
if (amount <= 0 || amount > getBalance() + overdraftLimit) {
System.out.println("Withdrawal refused");
return false;
}
changeBalance(-amount);
return true;
}
}The parent offers a protected method changeBalance so that children can move the balance without the field itself being exposed. Using it in main:
Account plain = new Account("Sara", 100);
CheckingAccount chk = new CheckingAccount("Omar", 100, 200);
plain.withdraw(250);
chk.withdraw(250);
System.out.println(plain.getBalance());
System.out.println(chk.getBalance());
chk.withdraw(100);
System.out.println(chk.getBalance());Output:
Withdrawal refused
100.0
-150.0
Withdrawal refused
-150.0The plain account refuses 250. The checking account allows it (100 plus an overdraft of 200 covers it) and lands at -150.0. The next 100 would need more than the 50 of room left, so it is refused.
Example 3: a fee override that breaks the promise (realistic)
A savings account should charge a flat fee of 1 on each withdrawal. A first attempt reuses the parent through super:
class SavingsAccount extends Account {
SavingsAccount(String owner, double balance) {
super(owner, balance);
}
@Override
boolean withdraw(double amount) {
boolean ok = super.withdraw(amount);
if (ok) {
changeBalance(-1); // flat fee
}
return ok;
}
}With a balance of 100, withdraw(100) is accepted by the parent (100 is not more than 100), then the fee is taken. The balance printed afterwards is:
-1.0A savings account is now below zero, and no part of the code looked wrong. The override must check the total cost first:
class SavingsAccount extends Account {
private static final double FEE = 1;
SavingsAccount(String owner, double balance) {
super(owner, balance);
}
@Override
boolean withdraw(double amount) {
if (amount + FEE > getBalance()) {
System.out.println("Withdrawal refused");
return false;
}
super.withdraw(amount);
changeBalance(-FEE);
return true;
}
}The same call now prints Withdrawal refused and the balance stays 100.0. Lesson: reusing the parent with super does not make an override safe. Think about what the parent promises.
Backend Insight
Overriding is the main way backend frameworks let you customize behavior: a base class defines a method that does a standard job, and you override it to change one step. Two professional habits follow:
- Always use
@Override. In a large codebase a base class method may be renamed. With the annotation, the compiler flags every override that no longer matches; without it, your customization silently stops being called. - Treat an override as a contract. Code written against the parent type trusts the parent's rules. A subtle override that breaks them (like the fee bug) can be hard to find, because the failure appears far away from the override.
Common Mistakes
Mistake 1: Omitting @Override
A typo or a changed parameter type creates a new method and the intended override never runs. Always add the annotation.
Mistake 2: Changing the parameters and calling it an override
withdraw(int amount) does not override withdraw(double amount). It is an overload. With @Override the compiler would refuse it.
Mistake 3: Narrowing the access
An override declared with less access than the parent's method is a compile error. Keep the same modifier or widen it.
Mistake 4: Trying to override a private or static method
A private method is invisible to the child, and a static method is hidden, not overridden. Neither gives you the replace-the-behavior effect you wanted.
Mistake 5: Calling the parent's code incorrectly
Writing withdraw(amount) inside the override of withdraw calls itself and recurses forever. To call the parent's version, write super.withdraw(amount).
Exercises
Try each exercise yourself before opening the solutions.
Level A: Foundation
A1. What three things must match for a subclass method to override an inherited instance method?
أظهر الحل A1
A1. The name, the parameter types (number, types, order), and the return type (or a compatible subtype). Access must also be the same or wider.
A2. What does @Override do, and what happens at compile time if there is no matching parent method?
أظهر الحل A2
A2. It tells the compiler that the method is meant to override a parent method. If none matches, the compiler reports an error: method does not override or implement a method from a supertype.
A3. The parent method is public void run(). Which of these overrides is legal: void run() or public void run()?
أظهر الحل A3
A3. Only public void run(). Dropping public narrows the access and is a compile error.
Level B: Application
B1. Shape has String name() { return "shape"; }. Write Circle extends Shape that overrides name() to return "circle", then a second override of a describe() method that uses super.describe() plus " (round)", given that Shape.describe() returns "I am a " + name(). What does new Circle().describe() return?
أظهر الحل B1
B1.
class Circle extends Shape {
@Override
String name() { return "circle"; }
@Override
String describe() { return super.describe() + " (round)"; }
}super.describe() returns "I am a " + name(). Since the object is a Circle, name() gives circle, so the result is I am a circle (round).
B2. Find the bug and fix it: the author wants Employee.bonus() to return a double and the manager version to add 500.
class Employee { double bonus() { return 100; } }
class Manager extends Employee {
double bonus() { return bonus() + 500; }
}أظهر الحل B2
B2. bonus() inside Manager.bonus() calls itself forever (it ends with a stack overflow). Use super.bonus() + 500 and add @Override: @Override double bonus() { return super.bonus() + 500; }. A manager's bonus is then 600.
B3. Parent has private void log(). Child declares @Override void log() { }. Predict the outcome.
أظهر الحل B3
B3. A compile error: method does not override or implement a method from a supertype, because a private method is not inherited and so cannot be overridden. Without @Override it would compile as an unrelated method.
Level C: Challenge
C1. A PremiumAccount extends Account overrides withdraw to allow withdrawing up to the balance plus 50 without recording the extra 50 as overdraft anywhere. Describe two problems with this override relative to the parent's promise.
أظهر الحل C1
C1. (1) The parent promises the balance never goes below zero, and the override lets it go 50 below with no rule limiting or recording it, so the balance can silently become negative. (2) Code that trusts Account (for example reports that assume a non-negative balance) can now see a value it was never designed for, so a failure can appear far from the override. If a bank really wants an overdraft, it should be an explicit, bounded, named feature as in CheckingAccount.
C2. Why does the compiler require an override to be at least as accessible as the original? Give a short example of what could go wrong otherwise.
أظهر الحل C2
C2. Code written against the parent may call the method because it is accessible to it. If a child narrowed the access, that same call on a child object would suddenly be forbidden. For example, a public run() replaced with a private run() would let the call compile against the parent type but be illegal for a child instance, so the compiler forbids narrowing.
Mini Challenge
Take the Account and CheckingAccount from Example 2. Add a describe() method to Account that returns owner + ": " + balance. In CheckingAccount, override it to return super.describe() + " (overdraft " + overdraftLimit + ")". Then deliberately misspell the override as descibe() with @Override and read the compiler message. Finally, remove @Override and notice that the program compiles, but your new text never appears.
Check your work: you saw both outcomes of the typo: a compile error with @Override, and silently wrong output without it.
Summary & Next Lesson
Key takeaways
- Overriding means the same signature, a new body, in a subclass.
- Always write
@Overrideso the compiler catches typos and signature mismatches. - An override cannot narrow access; private methods and constructors cannot be overridden; static methods are hidden, not overridden.
super.method()calls the parent's version.- An override must keep the parent's promises, and reusing
superdoes not guarantee that.
Next lesson
The next lesson, "final classes and methods", shows how a class author forbids overriding or extending when it would be unsafe.
Self-check
- What is the difference between overriding and overloading?
- What does
@Overrideprotect you from? - How do you call the parent's version of a method from the override?
- Can a private method be overridden?
- Why did the fee override in Example 3 produce a negative balance?
أظهر الإجابات
- Overriding replaces an inherited method with the same signature. Overloading adds a method with the same name and different parameters.
- Mistyped names or wrong parameters that silently create a new method.
- With
super.methodName(...). - No. It is not inherited.
- It let the parent's check pass first and then subtracted the fee without checking that the balance could cover it.
References
- Oracle, Java Tutorials, Overriding and Hiding Methods. Supports the override rules, covariant return types, access that cannot be narrower, the
@Overrideannotation and its compile-time error, and static method hiding. - The compiler messages and outputs were produced by compiling and running the code with JDK 21. The "what Java does with a private method of the same name" statement was checked by compiling it; the account examples and fee scenario are this course's own.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.