Java Tutorial » Chapter 27 — Interfaces

Chapter 27 — Java Interfaces

Defining contracts for classes to achieve full abstraction and multiple inheritance of type.

1. Introduction to Interfaces

What is an Interface?

An interface in Java is a reference type, similar to a class, that can contain only constants, method signatures, default methods, static methods, and nested types. It cannot contain instance fields or constructors. Method bodies exist only for default methods and static methods.

Think of an interface as a blueprint or a contract. It defines a set of methods that a class must implement, but it doesn't specify *how* those methods should be implemented. Any class that "signs" this contract (i.e., `implements` the interface) guarantees that it will provide an implementation for all the methods defined in the interface.

Interfaces are a key mechanism for achieving abstraction and a form of multiple inheritance in Java.

2. Declaring Interfaces

Creating a Contract

You declare an interface using the interface keyword. By default, all methods in an interface are public abstract, and all fields are public static final. You don't need to use these keywords, but it's good practice to be explicit for clarity.

// A simple interface defining a contract for anything that can be driven.
public interface Drivable {
    // This is a constant. It is implicitly 'public static final'.
    int MAX_SPEED = 200; 

    // These are abstract method signatures. They are implicitly 'public abstract'.
    void accelerate(int increment);
    void brake(int decrement);
    
    // A default method (since Java 8). Provides a default implementation.
    default void startEngine() {
        System.out.println("Engine started with a default roar.");
    }
    
    // A static method (since Java 8). Belongs to the interface, not an instance.
    static void checkVehicleStatus() {
        System.out.println("Performing general vehicle diagnostics...");
    }
}
3. Implementing Interfaces

Fulfilling the Contract

A class uses the implements keyword to implement an interface. This creates an "is-a" relationship. For example, a `Car` is a `Drivable` object. When a class implements an interface, it must provide a concrete implementation for all of the interface's abstract methods.

A key advantage is that a class can implement multiple interfaces, allowing it to fulfill multiple contracts.

// The Car class fulfills the Drivable contract
public class Car implements Drivable {
    private int currentSpeed = 0;

    // The Car class MUST provide an implementation for accelerate()
    @Override
    public void accelerate(int increment) {
        currentSpeed += increment;
        if (currentSpeed > MAX_SPEED) {
            currentSpeed = MAX_SPEED;
        }
        System.out.println("Car accelerating. Current speed: " + currentSpeed + " km/h");
    }

    // The Car class MUST provide an implementation for brake()
    @Override
    public void brake(int decrement) {
        currentSpeed -= decrement;
        if (currentSpeed < 0) {
            currentSpeed = 0;
        }
        System.out.println("Car braking. Current speed: " + currentSpeed + " km/h");
    }
    
    // The Car class inherits the default startEngine() method, but can override it
    @Override
    public void startEngine() {
        System.out.println("Car's V8 engine rumbles to life!");
    }
}

// A class can implement multiple interfaces
interface Flyable { void fly(); }

public class FlyingCar implements Drivable, Flyable {
    @Override
    public void accelerate(int increment) { /* ... implementation ... */ }
    @Override
    public void brake(int decrement) { /* ... implementation ... */ }
    @Override
    public void fly() {
        System.out.println("Flying car is taking to the skies!");
    }
}
4. Extending Interfaces

Inheriting a Contract

Just like classes, interfaces can use the extends keyword to inherit from another interface. This is useful for creating more specialized contracts from more general ones. An interface that extends another interface inherits all of its abstract methods.

For example, a `Drivable` interface is general. A `MotorcycleDrivable` interface could extend it to add methods specific to motorcycles, like `doWheelie()`.

// Base interface
public interface Drivable {
    void accelerate(int increment);
    void brake(int decrement);
}

// A more specific interface that extends the base one
public interface MotorcycleDrivable extends Drivable {
    // This interface inherits accelerate() and brake()
    
    // And adds its own specific method
    void doWheelie();
}

// A class implementing the specialized interface must implement ALL methods
public class Motorcycle implements MotorcycleDrivable {
    @Override
    public void accelerate(int increment) {
        System.out.println("Motorcycle revs and accelerates.");
    }

    @Override
    public void brake(int decrement) {
        System.out.println("Motorcycle applies brakes.");
    }

    @Override
    public void doWheelie() {
        System.out.println("Motorcycle pops a wheelie!");
    }
}
5. Extending Multiple Interfaces

Combining Contracts

Unlike classes, which can only extend a single other class, an interface can extend multiple other interfaces. This allows you to combine several smaller contracts into one larger, more comprehensive one.

This is a powerful feature for composing behavior. You can create fine-grained, single-purpose interfaces and then combine them as needed.

// A series of small, single-purpose interfaces
interface Printable { void print(); }
interface Scannable { void scan(); }
interface Faxable { void fax(String number); }

// A new interface that combines all three
public interface MultiFunctionDevice extends Printable, Scannable, Faxable {
    // This interface now has the methods print(), scan(), and fax()
}

// A class implementing the combined interface must provide all implementations
public class OfficePrinter implements MultiFunctionDevice {
    @Override
    public void print() {
        System.out.println("Printing document...");
    }

    @Override
    public void scan() {
        System.out.println("Scanning document...");
    }

    @Override
    public void fax(String number) {
        System.out.println("Faxing document to " + number);
    }
}
6. Tagging Interfaces

Marker Interfaces

A tagging interface (or marker interface) is a special kind of interface that has no methods or constants. Its sole purpose is to "mark" or "tag" a class as having a certain property or capability. Other code can then check for this tag using the instanceof operator to treat the class specially.

This is a powerful design pattern used by the Java core libraries. For example:

  • java.io.Serializable: Marks a class as eligible for serialization (converting its state to a byte stream).
  • java.lang.Cloneable: Marks a class as being allowed to create a field-for-field copy of its instances using the `clone()` method.
  • java.util.RandomAccess: Marks a `List` implementation (like `ArrayList`) that provides fast random access.
// A custom tagging interface to mark objects as secure for logging
public interface Loggable {
    // This interface is intentionally empty
}

// A class that is marked as Loggable
public class UserAccount implements Loggable {
    private String username;
    // ... fields and methods ...
}

// A class that is NOT marked as Loggable
public class SystemConfiguration {
    private String adminPassword;
    // ... fields and methods ...
}

// A method that uses the tagging interface
public class AuditLogger {
    public static void logObject(Object obj) {
        // Check if the object is tagged as Loggable
        if (obj instanceof Loggable) {
            System.out.println("AUDIT LOG: Logging object " + obj.getClass().getName());
        } else {
            System.out.println("INFO: Object " + obj.getClass().getName() + " is not loggable.");
        }
    }
}
7. Practice & Challenge

Test Your Skills

  1. Create an interface `Drawable` with a method `draw()`.
  2. Create another interface `Resizable` with a method `resize(double scale)`.
  3. Create a class `Shape` that implements both `Drawable` and `Resizable`. Provide implementations for both methods.
  4. In a `main` method, create a `Shape` object and call both `draw()` and `resize()` on it.

🏆 Challenge: Smart Home Device System

Design a system for a smart home using interfaces to model different device capabilities.

  • Create a tagging interface `SmartDevice`.
  • Create an interface `PowerControllable` with methods `turnOn()` and `turnOff()`.
  • Create an interface `Dimmable` that extends `PowerControllable` and adds a method `setBrightness(int level)`.
  • Create a class `SmartLight` that implements `SmartDevice` and `Dimmable`.
  • Create a class `SmartPlug` that implements `SmartDevice` and `PowerControllable`.
  • Create a `SmartHomeController` class with a method `addDevice(SmartDevice device)` and `turnOffAllDevices()`. The `turnOffAllDevices` method should use `instanceof` to check if a device is `PowerControllable` and, if so, turn it off.

// --- Interfaces ---
public interface SmartDevice {} // Tagging interface

public interface PowerControllable {
    void turnOn();
    void turnOff();
    boolean isOn();
}

public interface Dimmable extends PowerControllable {
    void setBrightness(int level); // level from 0 to 100
    int getBrightness();
}

// --- Implementations ---
public class SmartLight implements SmartDevice, Dimmable {
    private boolean on = false;
    private int brightness = 0;

    @Override
    public void turnOn() { on = true; System.out.println("SmartLight is ON"); }
    @Override
    public void turnOff() { on = false; System.out.println("SmartLight is OFF"); }
    @Override
    public boolean isOn() { return on; }

    @Override
    public void setBrightness(int level) {
        if (on && level >= 0 && level <= 100) {
            this.brightness = level;
            System.out.println("SmartLight brightness set to " + level + "%");
        }
    }
    @Override
    public int getBrightness() { return brightness; }
}

public class SmartPlug implements SmartDevice, PowerControllable {
    private boolean on = false;

    @Override
    public void turnOn() { on = true; System.out.println("SmartPlug is ON"); }
    @Override
    public void turnOff() { on = false; System.out.println("SmartPlug is OFF"); }
    @Override
    public boolean isOn() { return on; }
}

// --- Controller ---
import java.util.ArrayList;
import java.util.List;

public class SmartHomeController {
    private List devices = new ArrayList<>();

    public void addDevice(SmartDevice device) {
        devices.add(device);
    }

    public void turnOffAllDevices() {
        System.out.println("--- Turning off all controllable devices ---");
        for (SmartDevice device : devices) {
            // Check if the device "has a" PowerControllable capability
            if (device instanceof PowerControllable) {
                PowerControllable controllableDevice = (PowerControllable) device;
                if (controllableDevice.isOn()) {
                    controllableDevice.turnOff();
                }
            }
        }
    }
}