الدوال الافتراضية والثابتة في الواجهات
في هذا الدرس 11 موضوعات
هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.
Lesson Overview
An interface that is already used by dozens of classes has a problem: you want to add one more method, but every class that implements it would stop compiling until someone writes that method. Before Java 8, the only options were to never change a published interface or to break everyone.
Java's answer is the default method: a method with a body, written inside the interface. Classes that do not override it simply inherit it. Think of a socket standard that adds a new optional feature: old plugs still fit, and new plugs may use the feature.
Interfaces can also hold static methods: small helpers that belong with the interface and are called on its name.
Why this matters: default methods are widely used in Java's own library, and they appear in the interfaces you will meet in backend frameworks. They are also easy to overuse, so this lesson spends as much time on the risks as on the syntax.
This lesson builds on interfaces (lesson 1), abstract classes (lesson 2), and the decision guide (lesson 3).
Learning Objectives
By the end of this lesson you will be able to:
- Write a
defaultmethod in an interface and explain what implementers inherit. - Override a default method in a class.
- Explain why default methods were added: to evolve an interface without breaking implementers.
- Write a
staticinterface method and call it correctly. - Name two risks of default methods and show the compile error for one.
- Resolve a conflict between two defaults with
Interface.super.method(). - Decide when a default method is appropriate and when it is a warning sign.
Written Explanation
A default method
interface Shape {
double area();
default String describe() { // has a body
return "Area: " + area();
}
}Every class that implements Shape receives describe() for free. It can use the abstract method area() because, at run time, the object will be a real class that has written area().
A class may keep the inherited version or override it:
class Disk implements Shape {
@Override public double area() { ... }
@Override public String describe() { return "Disk with area ..."; }
}Note that, like every interface method, a default method is public.
Why Java added them
The Java tutorial explains the purpose: default methods let you add new functionality to an existing interface without breaking classes that already implement it. Without them, a new abstract method would force every implementing class to be rewritten. The library's own Comparator interface gained methods this way.
You can see the effect directly. Suppose Notifier shipped with only send, and ConsoleNotifier was compiled against it. Now the interface gains a default method:
public interface Notifier {
void send(String to, String message);
default void sendAll(String[] recipients, String message) {
for (String r : recipients) send(r, message);
}
}ConsoleNotifier needs no change: it already has send, and it gains sendAll automatically. In a test run for this lesson, the old ConsoleNotifier.class file (compiled before the interface changed) worked unmodified with the new interface.
A static interface method
static boolean isValidRecipient(String to) {
return to != null && !to.isBlank();
}A static method in an interface belongs to the interface itself. You call it on the interface name: Notifier.isValidRecipient("ana"). Per the Java tutorial it is not inherited by implementing classes, so consoleNotifier.isValidRecipient(...) does not compile. Use static methods for small helpers closely tied to the contract, such as validation or factory-style helpers.
Risks
Risk 1: Conflicting defaults. If a class implements two unrelated interfaces that both supply a default method with the same signature, Java does not guess:
interface Alarm { default String signal() { return "beep"; } }
interface Light { default String signal() { return "flash"; } }
class Device implements Alarm, Light { }error: types Alarm and Light are incompatible;
class Device inherits unrelated defaults for signal() from types Alarm and LightThe class must override the method. It can call a chosen version with Alarm.super.signal(). As a rule of thumb from the Java Language Specification: a method inherited from a superclass wins over an interface default, and a default in a more specific sub-interface wins over the one it extends. For unrelated interfaces, you decide.
Risk 2: Logic hides in interfaces. An interface is supposed to be a short contract that you can read in ten seconds. When defaults grow into pages of code, the real behavior is hard to find, and a change in one default silently changes every implementer. A default also cannot use object state, because interfaces have no instance fields, so it can only work through the abstract methods.
Risk 3: A default that is wrong for some class. A default is inherited by every implementer that does not override it. If isFree() is defined as amountDue() == 0, a class where that is the wrong rule silently gets the wrong answer. Unlike an abstract method, forgetting to override produces no error.
When to use a default
| Good use | Warning sign |
|---|---|
| Adding a method to an interface that already has implementers | The default is long or has branches |
A convenience built entirely from the abstract methods (describe() from area()) | The default tries to keep state |
A sensible, safe "do the common thing" such as sendAll calling send in a loop | You are using it to avoid writing an abstract class when you really need shared fields |
Keep interfaces small. If you need fields or a long shared algorithm, the previous lessons already told you the tool: an abstract class.
Default methods and abstract classes
Default methods do not replace abstract classes. An abstract class can hold state and constructors; a default method cannot. They solve different problems: defaults mainly help an interface grow safely.
Examples
Example 1: A convenience default (simple)
interface Shape {
double area();
default String describe() { return "Area: " + area(); }
}
class Square implements Shape {
private final double side;
Square(double side) { this.side = side; }
@Override public double area() { return side * side; }
}
class Disk implements Shape {
private final double r;
Disk(double r) { this.r = r; }
@Override public double area() { return Math.PI * r * r; }
@Override public String describe() {
return String.format("Disk with area %.1f", area());
}
}
public class Ex1 {
public static void main(String[] args) {
Shape[] ss = { new Square(3), new Disk(1) };
for (Shape s : ss) System.out.println(s.describe());
}
}Area: 9.0
Disk with area 3.1Square uses the inherited default. Disk replaces it.
Example 2: Evolving an interface (practical)
public interface Notifier {
void send(String to, String message);
default void sendAll(String[] recipients, String message) {
for (String r : recipients) send(r, message);
}
static boolean isValidRecipient(String to) {
return to != null && !to.isBlank();
}
}ConsoleNotifier is unchanged and prints through send. In main:
Notifier n = new ConsoleNotifier();
n.sendAll(new String[] { "ana", "omar" }, "Statement ready");
System.out.println(Notifier.isValidRecipient(" "));
System.out.println(Notifier.isValidRecipient("ana"));[console] ana: Statement ready
[console] omar: Statement ready
false
trueThe default calls the abstract send once per recipient, and the static helper is called on the interface name.
Example 3: Resolving a conflict, and the static trap (realistic)
The Device class from the risk section does not compile. Resolve it explicitly:
class Device implements Alarm, Light {
@Override public String signal() {
return Alarm.super.signal() + "+" + Light.super.signal();
}
}System.out.println(new Device().signal()); prints:
beep+flashAnd a static method is not inherited:
interface Notifier { static boolean ok(String s) { return true; } }
class Console implements Notifier { }
// new Console().ok("x")error: cannot find symbol
symbol: method ok(String)
location: variable c of type ConsoleWrite Notifier.ok("x") instead.
Backend Insight
Real interfaces in backend libraries gain new methods over the years, and defaults are how those libraries stay compatible with code written long before. As an application developer, you will mostly use defaults other people wrote, and occasionally add one to your own interface when a published contract must grow.
A good policy for your own code: if the interface is only used inside your project and you can edit every implementer, just add an abstract method and fix the compile errors. The compiler is then a safety net that finds every class that needs updating. Use a default when you cannot edit all implementers (a shared library) or when the method really is a safe convenience built from the others.
Common Mistakes
Mistake 1: Keeping state in an interface
- Incorrect: expecting
int countin an interface to be a per-object counter. It is a constant (public static final). - Correct: put per-object state in a class, such as an abstract base class.
Mistake 2: Calling a static interface method on an object
- Incorrect:
consoleNotifier.isValidRecipient("a") - Correct:
Notifier.isValidRecipient("a")
Mistake 3: Leaving a conflict unresolved
Two unrelated interfaces with the same default method give a compile error. Override the method in the class and choose with Interface.super.method().
Mistake 4: Turning every abstract method into a default
A default that returns 0 or does nothing silently hides a forgotten override, the same problem abstract methods were created to prevent. Use default only when the default is truly right for every implementer.
Mistake 5: A "god" interface with defaults for everything
Piling on defaults to make an interface convenient eventually produces a large, unfocused interface. Keep each interface small.
Exercises
Try each exercise yourself before opening the solutions.
Level A: Foundation
A1. What word marks an interface method that has a body and is inherited by implementers?
أظهر الحل A1
A1. default.
A2. Why were default methods added to Java?
أظهر الحل A2
A2. To let an interface gain a new method without breaking the classes that already implement it.
A3. How do you call a static interface method sum declared in Payable?
أظهر الحل A3
A3. On the interface name: Payable.sum(items).
Level B: Application
B1. Take interface Payable { double amountDue(); }. Add a default method isFree() that returns true when the amount due is zero, and a static method sum(Payable[] items) that totals the amounts. Create Invoice (fixed total) and Voucher (always 0) and print the result of isFree() for both and Payable.sum(...) for the pair.
أظهر الحل B1
B1. (Compiled and run.)
interface Payable {
double amountDue();
default boolean isFree() { return amountDue() == 0; }
static double sum(Payable[] items) {
double t = 0;
for (Payable p : items) t += p.amountDue();
return t;
}
}
class Invoice implements Payable {
private final double total;
Invoice(double total) { this.total = total; }
@Override public double amountDue() { return total; }
}
class Voucher implements Payable {
@Override public double amountDue() { return 0; }
@Override public boolean isFree() { return true; }
}With new Invoice(25) and new Voucher(), isFree() printed false and true, and Payable.sum(items) printed 25.0. (The override in Voucher is optional, since the default already gives true.)
B2. Two interfaces Printer and Scanner2 each have default String name() { return "..."; }. A class implements both. What does the compiler say, and what are two ways to fix it?
أظهر الحل B2
B2. An error saying the class inherits unrelated defaults for name() from both interfaces. Fix 1: override name() in the class and choose one with Printer.super.name(). Fix 2: override and write your own result (or combine both).
B3. Is a default method allowed to read a field of the implementing class directly? Explain.
أظهر الحل B3
B3. No. An interface does not know the fields of its implementers. A default can use only the interface's own methods (and constants), so the implementing class must expose what is needed through an abstract method.
Level C: Challenge
C1. A library publishes interface Exporter { String export(String data); }, and thousands of classes implement it. The author wants to add String extension(). Compare adding it as abstract versus as a default (returning "txt"). Which one would you choose and what is the risk of your choice?
أظهر الحل C1
C1. Abstract: every implementer fails to compile until it adds extension(), which is safe for correctness but breaks thousands of classes. Default "txt": nothing breaks, but classes for which "txt" is wrong keep silently returning it. Because this is a published library where implementers cannot all be edited, the default is the sensible choice here, and the risk is documented and accepted. In your own small project, abstract would be better.
C2. A teammate wants to put a 40-line algorithm with several helper steps into a default method. Give two reasons to move it elsewhere, and say where.
أظهر الحل C2
C2. (1) A long default hides behavior inside an interface, where it is hard to find, review and test. (2) A default cannot hold state or call private object helpers, so the algorithm will be awkward. Move it to an abstract class (if it needs state or protected helpers) or to a separate class that the implementers use.
Mini Challenge
Take the Notifier interface from lesson 1 and add:
- A default method
sendAll(String[] recipients, String message). - A static method
isValidRecipient(String to). - A second, deliberately conflicting interface
Loudwith a defaultsendAllof the same signature, and a class implementing both.
Compile, read the conflict message, then fix it with Notifier.super.sendAll(...).
Check yourself: you can say in one sentence why the default exists, and in another sentence why the static method is called on Notifier and not on an object.
Summary & Next Lesson
Key takeaways
- A
defaultmethod has a body inside an interface and is inherited by every implementer, who may override it. - Defaults exist so that interfaces can grow without breaking existing classes.
- A
staticinterface method is a helper called on the interface name and is not inherited. - Risks: conflicting defaults (a compile error you resolve with
Interface.super.method()), logic hidden in interfaces, and a default that is wrong for some implementer without any warning. - Defaults cannot use object state. When state or a long algorithm is needed, use an abstract class.
- Keep interfaces small.
Next lesson
The next lesson, "Programming to an interface", shows how to write code that depends on the contract instead of a particular class, so that you can swap implementations in one place.
Self-check
- What does a
defaultmethod have that a normal interface method does not? - Why were default methods added?
- What happens if a class inherits the same default from two unrelated interfaces?
- Can you call a static interface method on an object of an implementing class?
- Why can a default method not use a field of the implementing class directly?
أظهر الإجابات
- A method body.
- To add methods to an existing interface without breaking existing implementers.
- A compile error. The class must override the method and may choose a version with
Interface.super.method(). - No. Call it on the interface name.
- The interface knows nothing about the fields of its implementers; it can only call its own methods.
References
- Oracle, The Java Tutorials: Default Methods. Supports the purpose of default methods, the
Comparatorexample, and the rules for static interface methods (not inherited, called on the interface name). - Oracle, Java Language Specification SE 21, Chapter 9: Interfaces. Supports the statement that default methods have bodies and are not abstract. The conflict-resolution rules (class wins, more specific interface wins, otherwise the class must override) were summarised from the JLS by the fetch tool but not read directly in that page's excerpt, so treat the exact wording as unverified. The compile error text itself was observed by compiling with JDK 21.
- All code and output above come from compiling and running the examples with JDK 21.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.