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

الواجهات كعقود

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

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

Lesson Overview

In the last module you saw polymorphism: one call, many behaviors. You also saw its weak spot. A parent method with an empty "do nothing" body lets a subclass forget to override it, and the program quietly does the wrong thing. This module gives you tools to say "every type must supply this behavior" and have the compiler enforce it.

The first tool is the interface. Think of a wall socket: the wall does not care whether you plug in a lamp or a phone charger, as long as the plug fits. An interface is the shape of that socket. It lists what a class promises to do, and says nothing about how.

Why this matters: in a backend project, the code that processes an order should not care whether payment goes through one provider or another, or whether a message goes by email or SMS. Interfaces are how Java code expresses that.

This lesson builds on classes, extends, @Override, and polymorphism from the previous modules.

Learning Objectives

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

  • Declare an interface with one or more method signatures.
  • Implement an interface in a class with implements, and explain why every method must be written.
  • Implement the same interface in several different classes.
  • Use an interface as a variable, array, or parameter type.
  • Explain that a class can implement many interfaces while extending only one class.
  • Describe what is implicitly public, abstract, static, and final inside an interface.
  • Read the two most common compiler errors about interfaces.

Written Explanation

What an interface is

An interface is a type that lists methods without bodies. It is a contract: any class that signs it must provide every listed method.

java
interface Shape {
    double area();
}

There is no { ... } after area(), only a semicolon. That line says: "a Shape is anything that can report its area as a double". It does not say how to compute it.

A class signs the contract with implements and then writes each method:

java
class Circle implements Shape {
    private final double r;
    Circle(double r) { this.r = r; }
    @Override public double area() { return Math.PI * r * r; }
}

Use @Override here for the same reason as in the last module: the compiler checks that the method really implements something from the interface.

The Java tutorial describes interfaces as contracts: implementers may change their internals freely as long as they keep the published methods, and callers can be written against the interface without knowing the implementation.

The compiler enforces the contract

If a class says implements Shape but does not write area(), the code does not compile. Compare this with the empty-body default from the previous module: there, forgetting an override compiled and ran silently. Here, forgetting is impossible.

An interface is a type

You cannot write new Shape(): an interface has no code to run, so there is nothing to build. But you can use it as a type:

java
Shape[] shapes = { new Circle(1), new Rect(2, 3) };
for (Shape s : shapes) System.out.println(s.area());

A Shape variable can refer to any object whose class implements Shape. This is the same upcasting you met in the polymorphism module, now with an interface on the left. The call s.area() runs whichever version the real object has. Through a Shape variable you can call only the methods that Shape declares.

Rules for what is inside an interface

MemberWhat it means
Method without a bodyImplicitly public and abstract. You do not need to write either word.
FieldImplicitly public static final: a constant. Interfaces hold no per-object state.
default and static methodsAllowed, with bodies. Lesson 4 covers them.

Per the Java Language Specification, a method with no access modifier in an interface is public, and a field is public static final. The consequence you will meet first: when a class implements the method it must also write public, because a class method without a modifier is package-private, and that is weaker access than the interface promised.

Many interfaces, one parent class

A class can extend only one class, but it can implement any number of interfaces, separated by commas:

java
class SmsNotifier implements Notifier, Auditable { ... }

This is how Java lets one class play several roles without the tangles that come from inheriting state from several parents. Interfaces carry no instance state, so there is nothing to clash.

Naming and size

Name an interface after the role: Shape, Notifier, Payable. Keep it small and focused. An interface with one clear job is easy to implement and easy to fake in a test. An interface with ten unrelated methods (a "god" interface) forces every class to write methods it does not want. Tests and callers also become harder to read.

Where the analogy stops

A wall socket fixes the shape of the plug but not the safety of the device. An interface fixes the method signatures, but not what the methods do. A class can implement send by doing nothing at all. Meaning is documented in comments and checked by tests, not by the compiler.

Examples

Example 1: Shapes (simple)

java
interface Shape { double area(); }

class Circle implements Shape {
    private final double r;
    Circle(double r) { this.r = r; }
    @Override public double area() { return Math.PI * r * r; }
}
class Rect implements Shape {
    private final double w, h;
    Rect(double w, double h) { this.w = w; this.h = h; }
    @Override public double area() { return w * h; }
}
public class Ex1 {
    public static void main(String[] args) {
        Shape[] shapes = { new Circle(1), new Rect(2, 3) };
        for (Shape s : shapes) System.out.printf("%.2f%n", s.area());
    }
}

Output (compiled and run with JDK 21):

text
3.14
6.00

The loop treats both objects the same way. It does not use instanceof.

Example 2: One variable, different channels (practical)

java
interface Notifier { void send(String to, String message); }

class ConsoleNotifier implements Notifier {
    @Override public void send(String to, String message) {
        System.out.println("[console] " + to + ": " + message);
    }
}
class EmailNotifier implements Notifier {
    @Override public void send(String to, String message) {
        System.out.println("[email] To <" + to + "> body: " + message);
    }
}
public class Ex2 {
    public static void main(String[] args) {
        Notifier n = new ConsoleNotifier();
        n.send("ana", "Hello");
        n = new EmailNotifier();
        n.send("ana@example.com", "Hello");
    }
}

Output:

text
[console] ana: Hello
[email] To <ana@example.com> body: Hello

The variable n has type Notifier in both lines. Only the object changes. (Printing stands in for real sending; the lab at the end of this module uses this exact interface.)

Example 3: Two interfaces, and what the compiler says (realistic)

java
interface Notifier { void send(String to, String message); }
interface Auditable { String auditId(); int LIMIT = 3; }

class SmsNotifier implements Notifier, Auditable {
    @Override public void send(String to, String message) {
        System.out.println("[sms] " + to + ": " + message);
    }
    @Override public String auditId() { return "sms-1"; }
}
public class Ex3 {
    public static void main(String[] args) {
        SmsNotifier s = new SmsNotifier();
        Notifier n = s; Auditable a = s;
        n.send("555", "Hi");
        System.out.println(a.auditId() + " limit " + Auditable.LIMIT);
    }
}
text
[sms] 555: Hi
sms-1 limit 3

One object, two types: seen as a Notifier it can send, seen as Auditable it has an id. LIMIT is a constant, reached through the interface name.

Now the two mistakes you will make sooner or later.

Forgetting the method.

java
class PushNotifier implements Notifier { }
text
Bad1.java:2: error: PushNotifier is not abstract and does not override abstract method send(String,String) in Notifier

Forgetting public.

java
class PushNotifier implements Notifier {
    void send(String to, String message) { }
}
text
Bad2.java:3: error: send(String,String) in PushNotifier cannot implement send(String,String) in Notifier
    attempting to assign weaker access privileges; was public

And trying to build the interface itself:

text
error: Notifier is abstract; cannot be instantiated

Backend Insight

Almost every boundary in a Spring or Jakarta backend is an interface: a repository that stores data, a service that sends messages, a client that calls another system. The reason is the contract. The code that uses the interface is written once. The implementation behind it (one database today, another tomorrow, a fake in tests) can change without touching the callers.

A practical habit follows: when you design a new piece of a backend, first write the interface from the caller's point of view ("what do I need to ask for?"), and write the class second. Keeping the interface small makes the later steps much easier, for example creating a tiny fake implementation for a test.

Common Mistakes

Mistake 1: Trying to instantiate an interface

  • Incorrect: Notifier n = new Notifier();
  • Correct: Notifier n = new ConsoleNotifier();

The type on the left is the interface. The class after new is a real class.

Mistake 2: Leaving out public on the implementation

Interface methods are public. The implementing method must be too, or you get the "weaker access privileges" error shown above.

Mistake 3: Calling a class-only method through the interface type

If EmailNotifier has an extra method setSubject(...), a Notifier variable cannot call it. Only methods declared in the interface are visible. If the caller needs it, the method probably belongs in the interface, or the caller is depending on a concrete class more than it should.

Mistake 4: The "god" interface

A single Service interface with send, save, print, login, and delete forces every class to write methods that make no sense for it. Split it into small interfaces with one purpose each.

Mistake 5: Putting state in an interface

Fields in an interface are constants, not per-object data. If you need each object to hold its own values, that belongs in a class (the next lesson).

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. Write an interface Payable with one method that returns the amount due as a double.

أظهر الحل A1

A1.

java
interface Payable { double amountDue(); }

A2. Complete the sentence with a keyword: a class says it follows an interface using ____, while it inherits from a parent class using ____.

أظهر الحل A2

A2. implements; extends.

A3. True or false: Payable p = new Payable(); compiles if Payable has only one method.

أظهر الحل A3

A3. False. An interface cannot be instantiated: "Payable is abstract; cannot be instantiated".

Level B: Application

B1. Implement Payable in two classes: Invoice (a fixed total) and Subscription (monthly price times a number of months). Write a method static double total(Payable[] items) that adds up all amounts due.

أظهر الحل B1

B1. (Compiled and run.)

java
interface Payable { double amountDue(); }
class Invoice implements Payable {
    private final double total;
    Invoice(double total) { this.total = total; }
    @Override public double amountDue() { return total; }
}
class Subscription implements Payable {
    private final double monthly; private final int months;
    Subscription(double monthly, int months) { this.monthly = monthly; this.months = months; }
    @Override public double amountDue() { return monthly * months; }
}
static double total(Payable[] items) {
    double t = 0;
    for (Payable p : items) t += p.amountDue();
    return t;
}

With new Invoice(120.0) and new Subscription(9.5, 4) the total printed 158.0.

B2. Make Invoice also implement a second interface Named with String name(). Show how one Invoice object can be held by a Payable variable and by a Named variable.

أظهر الحل B2

B2.

java
interface Named { String name(); }
class Invoice implements Payable, Named {
    // fields, constructor and amountDue() as before
    @Override public String name() { return "Invoice for " + customer; }
}
Invoice inv = new Invoice("Omar", 10);
Payable p = inv;
Named n = inv;

Through n you can call name() only; through p, amountDue() only. It is the same object.

B3. A teammate writes double amountDue() { return 5; } in a class that implements Payable. Predict the compiler message and fix it.

أظهر الحل B3

B3. The compiler reports that amountDue() cannot implement the interface method because of weaker access privileges (it was public). The fix is public double amountDue() { return 5; }.

Level C: Challenge

C1. A colleague proposes one interface Worker with methods work(), sendEmail(), printReport(), payTax(). Explain two problems, and propose a better set of interfaces.

أظهر الحل C1

C1. Problems: (1) a class that only works, such as a Robot, must still write sendEmail and payTax, probably as empty or throwing methods, which brings back the silent-failure problem; (2) callers depend on far more than they use, so tests need to fake all four methods. Better: Workable, EmailSender, Reportable, TaxPayer; each class implements the ones it really supports.

C2. Why can total(Payable[] items) work with a class written next year, after total was already compiled and shipped?

أظهر الحل C2

C2. total is written against the contract (amountDue()), not against the classes. A new class that implements Payable can be put in the array, and the same compiled code calls its version of amountDue().

Mini Challenge

Choose something in a bank that comes in several kinds: a way of paying (card, cash, transfer), a way of exporting a report, or a way of checking a customer's identity. In under 20 lines:

  1. Write an interface with one focused method (name it for the role).
  2. Implement it in two classes, with printing in the method bodies.
  3. In main, keep one variable of the interface type, point it at each object in turn, and call the method.
  4. Break your code on purpose: delete public from one implementation and read the compiler message.

Check: your main never mentions a concrete class except after new.

Summary & Next Lesson

Key takeaways

  • An interface lists method signatures: a contract that says what, not how.
  • A class signs the contract with implements and must write every method, with public.
  • The interface works as a type: variables, arrays, and parameters can use it, and calls dispatch to the real object.
  • A class can implement many interfaces, but extend only one class.
  • Interface fields are constants. Interfaces hold no per-object state.
  • Keep interfaces small. A "god" interface forces classes to write methods they cannot support.

Next lesson

The next lesson, "Abstract classes", covers the other half of the toolkit: a parent class that is partly finished, with shared fields and code, and with methods that subclasses are forced to complete.

Self-check

  1. What keyword does a class use to follow an interface?
  2. Why does new Notifier() not compile when Notifier is an interface?
  3. A class implements an interface method but forgets public. What happens?
  4. Can one class implement two interfaces? Can it extend two classes?
  5. Through a Notifier variable, which methods can you call?
أظهر الإجابات
  1. implements.
  2. An interface has no code or state to build an object from; only a real class can be instantiated.
  3. Compile error: the method tries to assign weaker access than the interface's public method.
  4. Yes to two (or more) interfaces. No to two classes: Java allows one superclass.
  5. Only the methods declared in the Notifier interface.

References

  • Oracle, The Java Tutorials: Defining an Interface. Supports the contract idea, that interfaces cannot be instantiated, and that an implementing class must supply each method.
  • Oracle, Java Language Specification SE 21, Chapter 9: Interfaces. Supports implicit public and abstract methods and public static final fields. The page's multiple-interfaces rule (8.1.5) was confirmed from its introduction only; the Java Tutorial's abstract-class page also states that a class may implement any number of interfaces but extend only one class.
  • Oracle, The Java Tutorials: Abstract Methods and Classes. Supports the one-superclass versus many-interfaces comparison. The compiler messages and outputs above come from compiling and running the examples with JDK 21.

التطبيق

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

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

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

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