Java Interfaces

Complete guide to Java interfaces with examples. Learn interface declaration, implementation, multiple inheritance, default methods, and interface vs abstract class.

An interface in Java is a reference type that defines a contract of methods that a class must implement. Interfaces are used to achieve abstraction and multiple inheritance in Java, providing a way to specify what a class must do without defining how it should do it.

What is an Interface?

An interface is a collection of abstract methods (methods without implementation) and constants. It defines a contract that implementing classes must follow. Think of an interface as a blueprint that specifies what methods a class should have.

Basic Interface Syntax

interface InterfaceName {
    // Constant declarations (public, static, final by default)
    int CONSTANT_VALUE = 100;

    // Abstract method declarations (public and abstract by default)
    void method1();
    int method2(String parameter);
}

Creating and Implementing Interfaces

Example: Basic Interface

interface Animal {
    // Constants (implicitly public, static, final)
    String KINGDOM = "Animalia";

    // Abstract methods (implicitly public and abstract)
    void makeSound();
    void move();
    void eat();
}

class Dog implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks: Woof! Woof!");
    }

    @Override
    public void move() {
        System.out.println("Dog runs on four legs");
    }

    @Override
    public void eat() {
        System.out.println("Dog eats dog food");
    }
}

class Bird implements Animal {
    @Override
    public void makeSound() {
        System.out.println("Bird chirps: Tweet! Tweet!");
    }

    @Override
    public void move() {
        System.out.println("Bird flies in the sky");
    }

    @Override
    public void eat() {
        System.out.println("Bird eats seeds and insects");
    }
}

class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        Animal bird = new Bird();

        System.out.println("Kingdom: " + Animal.KINGDOM);

        System.out.println("\nDog behaviors:");
        dog.makeSound();
        dog.move();
        dog.eat();

        System.out.println("\nBird behaviors:");
        bird.makeSound();
        bird.move();
        bird.eat();
    }
}

Multiple Inheritance with Interfaces

Java doesn't support multiple inheritance of classes, but it supports multiple inheritance through interfaces. A class can implement multiple interfaces.

Example: Multiple Interface Implementation

interface Flyable {
    void fly();
    int getMaxAltitude();
}

interface Swimmable {
    void swim();
    int getMaxDepth();
}

interface Walkable {
    void walk();
    int getMaxSpeed();
}

class Duck implements Flyable, Swimmable, Walkable {
    @Override
    public void fly() {
        System.out.println("Duck flies at moderate altitude");
    }

    @Override
    public int getMaxAltitude() {
        return 1000; // meters
    }

    @Override
    public void swim() {
        System.out.println("Duck swims on water surface");
    }

    @Override
    public int getMaxDepth() {
        return 2; // meters
    }

    @Override
    public void walk() {
        System.out.println("Duck waddles on land");
    }

    @Override
    public int getMaxSpeed() {
        return 8; // km/h
    }
}

class Penguin implements Swimmable, Walkable {
    @Override
    public void swim() {
        System.out.println("Penguin swims underwater");
    }

    @Override
    public int getMaxDepth() {
        return 500; // meters
    }

    @Override
    public void walk() {
        System.out.println("Penguin walks on ice");
    }

    @Override
    public int getMaxSpeed() {
        return 5; // km/h
    }
}

class AnimalShowcase {
    public static void main(String[] args) {
        Duck duck = new Duck();
        Penguin penguin = new Penguin();

        System.out.println("=== Duck Abilities ===");
        duck.fly();
        duck.swim();
        duck.walk();
        System.out.println("Max altitude: " + duck.getMaxAltitude() + "m");

        System.out.println("\n=== Penguin Abilities ===");
        penguin.swim();
        penguin.walk();
        System.out.println("Max depth: " + penguin.getMaxDepth() + "m");
    }
}

Interface Inheritance

Interfaces can extend other interfaces using the extends keyword.

interface Vehicle {
    void start();
    void stop();
}

interface LandVehicle extends Vehicle {
    void accelerate();
    void brake();
}

interface WaterVehicle extends Vehicle {
    void sail();
    void anchor();
}

interface AmphibiousVehicle extends LandVehicle, WaterVehicle {
    void switchToLandMode();
    void switchToWaterMode();
}

class AmphibiousCar implements AmphibiousVehicle {
    private String mode = "land";

    @Override
    public void start() {
        System.out.println("Amphibious car engine started");
    }

    @Override
    public void stop() {
        System.out.println("Amphibious car engine stopped");
    }

    @Override
    public void accelerate() {
        System.out.println("Accelerating on land");
    }

    @Override
    public void brake() {
        System.out.println("Braking on land");
    }

    @Override
    public void sail() {
        System.out.println("Sailing on water");
    }

    @Override
    public void anchor() {
        System.out.println("Anchoring in water");
    }

    @Override
    public void switchToLandMode() {
        mode = "land";
        System.out.println("Switched to land mode");
    }

    @Override
    public void switchToWaterMode() {
        mode = "water";
        System.out.println("Switched to water mode");
    }
}

Default Methods (Java 8+)

Since Java 8, interfaces can have default methods with implementation. This allows adding new methods to interfaces without breaking existing implementations.

interface Drawable {
    // Abstract method
    void draw();

    // Default method
    default void print() {
        System.out.println("Printing: " + this.getClass().getSimpleName());
    }

    // Default method with logic
    default void displayInfo() {
        System.out.println("This is a drawable object");
        draw(); // Calling abstract method
    }
}

interface Colorable {
    String getColor();

    default void paintWithColor(String color) {
        System.out.println("Painting with " + color + " color");
    }
}

class Circle implements Drawable, Colorable {
    private String color;

    public Circle(String color) {
        this.color = color;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a circle");
    }

    @Override
    public String getColor() {
        return color;
    }
}

class Rectangle implements Drawable, Colorable {
    private String color;

    public Rectangle(String color) {
        this.color = color;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a rectangle");
    }

    @Override
    public String getColor() {
        return color;
    }

    // Overriding default method
    @Override
    public void print() {
        System.out.println("Custom printing for rectangle");
    }
}

class DefaultMethodExample {
    public static void main(String[] args) {
        Circle circle = new Circle("Red");
        Rectangle rectangle = new Rectangle("Blue");

        System.out.println("=== Circle ===");
        circle.draw();
        circle.print(); // Using default method
        circle.displayInfo(); // Using default method
        System.out.println("Color: " + circle.getColor());

        System.out.println("\n=== Rectangle ===");
        rectangle.draw();
        rectangle.print(); // Using overridden method
        rectangle.paintWithColor("Green"); // Using default method from Colorable
    }
}

Static Methods in Interfaces (Java 8+)

Interfaces can also have static methods that belong to the interface itself.

interface MathUtils {
    // Abstract method
    double calculate();

    // Static method
    static double add(double a, double b) {
        return a + b;
    }

    static double multiply(double a, double b) {
        return a * b;
    }

    static void printWelcome() {
        System.out.println("Welcome to MathUtils!");
    }
}

class Calculator implements MathUtils {
    private double result;

    public Calculator(double initialValue) {
        this.result = initialValue;
    }

    @Override
    public double calculate() {
        return result;
    }

    public void add(double value) {
        result = MathUtils.add(result, value); // Using static method
    }

    public void multiply(double value) {
        result = MathUtils.multiply(result, value); // Using static method
    }
}

class StaticMethodExample {
    public static void main(String[] args) {
        MathUtils.printWelcome(); // Calling static method directly

        Calculator calc = new Calculator(10);
        calc.add(5);
        calc.multiply(2);

        System.out.println("Result: " + calc.calculate());

        // Using static methods directly
        double sum = MathUtils.add(10, 20);
        double product = MathUtils.multiply(5, 6);
        System.out.println("Sum: " + sum + ", Product: " + product);
    }
}

Real-World Example: Payment System

interface PaymentProcessor {
    // Constants
    double MAX_TRANSACTION_AMOUNT = 10000.0;
    String CURRENCY = "USD";

    // Abstract methods
    boolean processPayment(double amount);
    String getPaymentMethod();
    double getTransactionFee(double amount);

    // Default method
    default boolean isValidAmount(double amount) {
        return amount > 0 && amount <= MAX_TRANSACTION_AMOUNT;
    }

    default void logTransaction(double amount) {
        System.out.println("Transaction logged: " + amount + " " + CURRENCY);
    }

    // Static method
    static String formatCurrency(double amount) {
        return String.format("%.2f %s", amount, CURRENCY);
    }
}

interface SecurePayment {
    boolean authenticate(String credentials);
    void encrypt();
    void decrypt();

    default boolean isSecure() {
        return true;
    }
}

class CreditCardProcessor implements PaymentProcessor, SecurePayment {
    private String cardNumber;

    public CreditCardProcessor(String cardNumber) {
        this.cardNumber = cardNumber;
    }

    @Override
    public boolean processPayment(double amount) {
        if (!isValidAmount(amount)) {
            System.out.println("Invalid amount: " + amount);
            return false;
        }

        encrypt();
        System.out.println("Processing credit card payment of " +
                         PaymentProcessor.formatCurrency(amount));
        decrypt();
        logTransaction(amount);
        return true;
    }

    @Override
    public String getPaymentMethod() {
        return "Credit Card";
    }

    @Override
    public double getTransactionFee(double amount) {
        return amount * 0.03; // 3% fee
    }

    @Override
    public boolean authenticate(String credentials) {
        System.out.println("Authenticating credit card...");
        return credentials.equals("valid_pin");
    }

    @Override
    public void encrypt() {
        System.out.println("Encrypting credit card data");
    }

    @Override
    public void decrypt() {
        System.out.println("Decrypting credit card data");
    }
}

class PayPalProcessor implements PaymentProcessor, SecurePayment {
    private String email;

    public PayPalProcessor(String email) {
        this.email = email;
    }

    @Override
    public boolean processPayment(double amount) {
        if (!isValidAmount(amount)) {
            System.out.println("Invalid amount: " + amount);
            return false;
        }

        System.out.println("Processing PayPal payment of " +
                         PaymentProcessor.formatCurrency(amount));
        logTransaction(amount);
        return true;
    }

    @Override
    public String getPaymentMethod() {
        return "PayPal";
    }

    @Override
    public double getTransactionFee(double amount) {
        return amount * 0.025; // 2.5% fee
    }

    @Override
    public boolean authenticate(String credentials) {
        System.out.println("Authenticating PayPal account...");
        return credentials.equals("valid_password");
    }

    @Override
    public void encrypt() {
        System.out.println("Encrypting PayPal data");
    }

    @Override
    public void decrypt() {
        System.out.println("Decrypting PayPal data");
    }
}

class PaymentSystem {
    public static void processPayments(PaymentProcessor[] processors, double amount) {
        System.out.println("=== Payment Processing System ===\n");

        for (PaymentProcessor processor : processors) {
            System.out.println("Payment Method: " + processor.getPaymentMethod());
            System.out.println("Transaction Fee: " +
                             PaymentProcessor.formatCurrency(processor.getTransactionFee(amount)));

            boolean success = processor.processPayment(amount);
            System.out.println("Payment Status: " + (success ? "SUCCESS" : "FAILED"));
            System.out.println("---");
        }
    }

    public static void main(String[] args) {
        PaymentProcessor[] processors = {
            new CreditCardProcessor("1234-5678-9012-3456"),
            new PayPalProcessor("user@example.com")
        };

        processPayments(processors, 250.0);
    }
}

Interface vs Abstract Class

FeatureInterfaceAbstract Class
MethodsAbstract by default (can have default/static)Can have both abstract and concrete methods
VariablesOnly constants (public, static, final)Can have instance variables
InheritanceMultiple inheritance supportedSingle inheritance only
ConstructorCannot have constructorsCan have constructors
Access ModifiersMethods are public by defaultCan have any access modifier
When to UseDefine contracts, achieve multiple inheritanceShare code among related classes

Example: When to Use Interface vs Abstract Class

// Use Interface: Define contract for unrelated classes
interface Printable {
    void print();
}

class Document implements Printable {
    public void print() { System.out.println("Printing document"); }
}

class Photo implements Printable {
    public void print() { System.out.println("Printing photo"); }
}

// Use Abstract Class: Share common code among related classes
abstract class Shape {
    protected String color;

    public Shape(String color) {
        this.color = color;
    }

    // Concrete method
    public void setColor(String color) {
        this.color = color;
    }

    // Abstract method
    public abstract double getArea();
}

class Circle extends Shape {
    private double radius;

    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}

Best Practices for Interfaces

1. Keep Interfaces Focused

// Good: Single responsibility
interface FileReader {
    String readFile(String path);
}

interface FileWriter {
    void writeFile(String path, String content);
}

// Avoid: Too many responsibilities
interface FileHandler {
    String readFile(String path);
    void writeFile(String path, String content);
    void deleteFile(String path);
    void copyFile(String source, String dest);
    // ... many more methods
}

2. Use Default Methods Carefully

interface Sortable {
    void sort();

    // Good: Providing useful default behavior
    default void sortDescending() {
        sort();
        reverse();
    }

    private void reverse() {
        // Helper method implementation
    }
}

3. Design for Extension

interface EventHandler<T> {
    void handle(T event);

    // Allows for future extension without breaking existing code
    default boolean canHandle(T event) {
        return true;
    }
}

Key Points to Remember

  • Interfaces define contracts that classes must implement
  • All methods in interfaces are public and abstract by default (before Java 8)
  • Interface variables are public, static, and final by default
  • Classes can implement multiple interfaces (multiple inheritance)
  • Interfaces can extend other interfaces
  • Default methods (Java 8+) provide implementations in interfaces
  • Static methods in interfaces belong to the interface itself
  • Use interfaces for contracts and abstract classes for shared implementation
  • Interfaces promote loose coupling and high cohesion
  • Polymorphism works seamlessly with interfaces

Best Practice: Program to interfaces, not implementations. This makes your code more flexible, testable, and maintainable.