Default Methods

Introduction

Default methods (Java 8+) allow interfaces to have methods with implementation. Also called defender methods or virtual extension methods.


Basic Syntax

interface InterfaceName {
    // Default method with body
    default returnType methodName() {
        // Method implementation
    }
}

Simple Example

interface Vehicle {
    // Abstract method
    void start();

    // Default method with implementation
    default void stop() {
        System.out.println("Vehicle stopped");
    }
}

class Car implements Vehicle {
    @Override
    public void start() {
        System.out.println("Car started");
    }

    // Can use default stop() or override it
}

public class Main {
    public static void main(String[] args) {
        Vehicle car = new Car();
        car.start();  // Car started
        car.stop();   // Vehicle stopped (using default)
    }
}

Why Default Methods?

Problem before Java 8:

  • Adding method to interface breaks all implementations
  • Cannot evolve interfaces without breaking code

Solution:

  • Default methods provide implementation
  • Old classes work without changes
  • New classes can override if needed

Key Features

  1. Keyword: default
  2. Has body (implementation)
  3. Can be overridden by implementing class
  4. Backward compatible - old code works
  5. Optional override - use default or override

Using Default Method

interface Printable {
    // Abstract method - must implement
    void print();

    // Default method - optional to override
    default void display() {
        System.out.println("Displaying...");
    }
}

// Class using default method
class Document1 implements Printable {
    @Override
    public void print() {
        System.out.println("Printing document1");
    }
    // Using default display()
}

// Class overriding default method
class Document2 implements Printable {
    @Override
    public void print() {
        System.out.println("Printing document2");
    }

    @Override
    public void display() {
        System.out.println("Custom display for document2");
    }
}

public class Main {
    public static void main(String[] args) {
        Printable d1 = new Document1();
        d1.display();  // Displaying... (default)

        Printable d2 = new Document2();
        d2.display();  // Custom display for document2 (overridden)
    }
}

Backward Compatibility Example

// Original interface
interface Calculator {
    int add(int a, int b);
    int subtract(int a, int b);
}

// Old implementation (already exists)
class BasicCalculator implements Calculator {
    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    public int subtract(int a, int b) {
        return a - b;
    }
}

// NOW: Add new method to interface
interface CalculatorV2 {
    int add(int a, int b);
    int subtract(int a, int b);

    // New default method - doesn't break BasicCalculator!
    default int multiply(int a, int b) {
        return a * b;
    }
}

public class Main {
    public static void main(String[] args) {
        // Old class still works with new interface
        Calculator calc = new BasicCalculator();
        System.out.println(calc.add(10, 5));       // 15
        System.out.println(calc.subtract(10, 5));  // 5
        // calc.multiply(10, 5);  // Also available if interface updated
    }
}

Calling Other Interface Methods

interface MathOperations {
    int add(int a, int b);
    int subtract(int a, int b);

    // Default method calling abstract methods
    default int addAll(int... numbers) {
        int sum = 0;
        for (int num : numbers) {
            sum = add(sum, num);
        }
        return sum;
    }

    // Default method calling another default method
    default void printSum(int... numbers) {
        System.out.println("Sum: " + addAll(numbers));
    }
}

class Calculator implements MathOperations {
    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    public int subtract(int a, int b) {
        return a - b;
    }
}

public class Main {
    public static void main(String[] args) {
        MathOperations calc = new Calculator();
        System.out.println(calc.addAll(1, 2, 3, 4, 5));  // 15
        calc.printSum(10, 20, 30);                       // Sum: 60
    }
}

Complete Example: List Interface Evolution

interface MyList<T> {
    void add(T item);
    T get(int index);
    int size();

    // Default method - new functionality without breaking existing code
    default boolean isEmpty() {
        return size() == 0;
    }

    default void addAll(T... items) {
        for (T item : items) {
            add(item);
        }
    }

    default void printAll() {
        System.out.println("List contents:");
        for (int i = 0; i < size(); i++) {
            System.out.println(get(i));
        }
    }

    default boolean contains(T item) {
        for (int i = 0; i < size(); i++) {
            if (get(i).equals(item)) {
                return true;
            }
        }
        return false;
    }
}

class ArrayList<T> implements MyList<T> {
    private Object[] array = new Object[10];
    private int count = 0;

    @Override
    public void add(T item) {
        array[count++] = item;
    }

    @Override
    @SuppressWarnings("unchecked")
    public T get(int index) {
        return (T) array[index];
    }

    @Override
    public int size() {
        return count;
    }

    // All default methods automatically available
}

public class Main {
    public static void main(String[] args) {
        MyList<String> list = new ArrayList<>();

        list.add("Apple");
        list.add("Banana");
        list.addAll("Cherry", "Date");  // Using default method

        System.out.println("Empty: " + list.isEmpty());        // false
        System.out.println("Contains Banana: " + list.contains("Banana"));  // true

        list.printAll();  // Using default method
    }
}

Overriding Default Method

interface Logger {
    default void log(String message) {
        System.out.println("LOG: " + message);
    }
}

class FileLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("FILE: " + message);
    }
}

class DatabaseLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("DB: " + message);
    }
}

class ConsoleLogger implements Logger {
    // Using default log method
}

public class Main {
    public static void main(String[] args) {
        Logger fileLog = new FileLogger();
        fileLog.log("Error occurred");  // FILE: Error occurred

        Logger dbLog = new DatabaseLogger();
        dbLog.log("User logged in");    // DB: User logged in

        Logger consoleLog = new ConsoleLogger();
        consoleLog.log("Info message");  // LOG: Info message
    }
}

Real-World Example: Shape Interface

interface Shape {
    double area();
    double perimeter();

    // Default method for display
    default void display() {
        System.out.println("Shape Information:");
        System.out.println("Area: " + area());
        System.out.println("Perimeter: " + perimeter());
    }

    // Default method for comparison
    default boolean isLargerThan(Shape other) {
        return this.area() > other.area();
    }

    // Default method for scaling
    default void printScaled(double factor) {
        System.out.println("Original area: " + area());
        System.out.println("Scaled area: " + (area() * factor * factor));
    }
}

class Circle implements Shape {
    private double radius;

    Circle(double radius) {
        this.radius = radius;
    }

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

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

    // Override display for custom format
    @Override
    public void display() {
        System.out.println("Circle with radius: " + radius);
        System.out.println("Area: " + area());
        System.out.println("Circumference: " + perimeter());
    }
}

class Rectangle implements Shape {
    private double length, width;

    Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public double area() {
        return length * width;
    }

    @Override
    public double perimeter() {
        return 2 * (length + width);
    }

    // Using default display method
}

public class Main {
    public static void main(String[] args) {
        Shape circle = new Circle(5);
        Shape rectangle = new Rectangle(4, 6);

        circle.display();
        System.out.println();

        rectangle.display();  // Using default
        System.out.println();

        System.out.println("Circle larger: " + circle.isLargerThan(rectangle));

        rectangle.printScaled(2);
    }
}

Default Methods with Fields

Note: Interfaces cannot have instance fields, but default methods can access abstract methods.

interface Identifiable {
    int getId();  // Abstract getter
    void setId(int id);  // Abstract setter

    // Default method using abstract methods
    default void printId() {
        System.out.println("ID: " + getId());
    }

    default boolean hasValidId() {
        return getId() > 0;
    }
}

class User implements Identifiable {
    private int id;

    @Override
    public int getId() {
        return id;
    }

    @Override
    public void setId(int id) {
        this.id = id;
    }
}

public class Main {
    public static void main(String[] args) {
        User user = new User();
        user.setId(101);
        user.printId();  // ID: 101
        System.out.println("Valid: " + user.hasValidId());  // true
    }
}

Default vs Static Methods

FeatureDefault MethodStatic Method
Keyworddefaultstatic
Belongs toObjectInterface
AccessThrough objectThrough interface name
OverrideYesNo
Call other methodsAbstract + defaultOnly static
Use caseInstance behaviorUtility methods
interface Example {
    // Default - belongs to object
    default void defaultMethod() {
        System.out.println("Default");
    }

    // Static - belongs to interface
    static void staticMethod() {
        System.out.println("Static");
    }
}

class MyClass implements Example { }

public class Main {
    public static void main(String[] args) {
        MyClass obj = new MyClass();
        obj.defaultMethod();      // ✓ OK

        Example.staticMethod();   // ✓ OK
        // obj.staticMethod();    // ✗ Error
    }
}

Benefits of Default Methods

  1. Backward compatibility - old code works
  2. Interface evolution - add methods without breaking
  3. Optional functionality - provide default behavior
  4. Code reuse - shared implementation
  5. Flexibility - override when needed

Common Use Cases

1. Iterator:

interface Iterator<T> {
    boolean hasNext();
    T next();

    default void remove() {
        throw new UnsupportedOperationException();
    }
}

2. Comparator:

interface Comparator<T> {
    int compare(T o1, T o2);

    default Comparator<T> reversed() {
        return (o1, o2) -> compare(o2, o1);
    }
}

3. Collection:

interface Collection<T> {
    boolean add(T element);

    default boolean addAll(Collection<T> c) {
        for (T element : c) {
            add(element);
        }
        return true;
    }
}

Quick Reference

interface MyInterface {
    // Abstract method
    void abstractMethod();

    // Default method
    default void defaultMethod() {
        System.out.println("Default implementation");
    }

    // Default calling abstract
    default void combinedMethod() {
        abstractMethod();  // Call abstract
        System.out.println("Additional logic");
    }
}

// Using default
class Class1 implements MyInterface {
    public void abstractMethod() { }
    // Uses default methods as-is
}

// Overriding default
class Class2 implements MyInterface {
    public void abstractMethod() { }

    @Override
    public void defaultMethod() {
        System.out.println("Custom implementation");
    }
}

Exam Tips

Remember:

  1. Default methods since Java 8
  2. Use default keyword
  3. Have method body
  4. Can be overridden
  5. Optional override - not mandatory
  6. Purpose: backward compatibility
  7. Can call abstract and default methods
  8. Cannot be static and default together
  9. Used for interface evolution
  10. Implementing class inherits default behavior

Common Questions:

  • What are default methods?
  • When were they introduced?
  • Why use default methods?
  • Can default methods be overridden?
  • Default vs abstract methods?
  • Default vs static methods?
  • Benefits of default methods?
  • Backward compatibility example?
  • Can default method call abstract method?
  • Is overriding default method mandatory?