Skip to main content
Interfaces
  1. Resources/
  2. Study Materials/
  3. Information & Communication Technology Engineering/
  4. ICT Semester 4/
  5. Java Programming (4343203)/
  6. Java Programming Slidev Presentations/

Interfaces

·
Milav Dabgar
Author
Milav Dabgar
Experienced lecturer in the electrical and electronic manufacturing industry. Skilled in Embedded Systems, Image Processing, Data Science, MATLAB, Python, STM32. Strong education professional with a Master’s degree in Communication Systems Engineering from L.D. College of Engineering - Ahmedabad.
Table of Contents

Interfaces
#

Lecture 23
#

Java Programming (4343203)
Diploma in ICT - Semester IV
Gujarat Technological University

Press Space for next page

layout: default
#

Learning Objectives
#

By the end of this lecture, you will be able to:

  • ๐ŸŽฏ Understand the concept and purpose of interfaces in Java
  • ๐Ÿ“‹ Declare interfaces with abstract, default, and static methods
  • ๐Ÿ”ง Implement interfaces in classes and handle multiple inheritance
  • ๐Ÿš€ Apply interface-based design patterns effectively
  • โšก Create functional interfaces for lambda expressions
  • ๐Ÿ“ Design flexible and maintainable code using interfaces
  • ๐Ÿ—๏ธ Practice advanced interface concepts and best practices

layout: default
#

What are Interfaces?
#

Definition
#

  • Interface is a contract that defines what a class can do
  • Contains method signatures without implementation (abstract methods)
  • Specifies behavior that implementing classes must provide
  • Supports multiple inheritance through implementation

Key Characteristics
#

  • All methods are implicitly public abstract (before Java 8)
  • All variables are implicitly public static final
  • Cannot be instantiated directly
  • Can contain default methods (Java 8+)
  • Can contain static methods (Java 8+)
  • Can contain private methods (Java 9+)

Interface vs Class vs Abstract Class
#

FeatureInterfaceAbstract ClassClass
InstantiationโŒโŒโœ…
Multiple Inheritanceโœ…โŒโŒ
ConstructorโŒโœ…โœ…
Instance VariablesโŒโœ…โœ…
Abstract Methodsโœ…โœ…โŒ
Concrete Methodsโœ…*โœ…โœ…

*Default methods (Java 8+)

Benefits
#

  • Abstraction: Hide implementation details
  • Multiple Inheritance: Implement multiple interfaces
  • Polymorphism: Reference by interface type
  • Loose Coupling: Depend on abstraction, not implementation

layout: default
#

Basic Interface Declaration
#

Simple Interface
#

public interface Drawable {
    // Abstract method (implicitly public abstract)
    void draw();
    
    // Constants (implicitly public static final)
    String DEFAULT_COLOR = "BLACK";
    int MAX_SIZE = 1000;
}

Interface Implementation
#

public class Circle implements Drawable {
    private double radius;
    private String color;
    
    public Circle(double radius) {
        this.radius = radius;
        this.color = Drawable.DEFAULT_COLOR;
    }
    
    @Override
    public void draw() {
        System.out.println("Drawing a circle with radius: " 
                         + radius + " and color: " + color);
    }
    
    public void setColor(String color) {
        this.color = color;
    }
}

Multiple Method Interface
#

public interface Shape {
    // Abstract methods
    double calculateArea();
    double calculatePerimeter();
    void display();
    
    // Constants
    String SHAPE_TYPE = "GEOMETRIC_SHAPE";
    double PI = 3.14159;
}

public class Rectangle implements Shape {
    private double length, width;
    
    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }
    
    @Override
    public double calculateArea() {
        return length * width;
    }
    
    @Override
    public double calculatePerimeter() {
        return 2 * (length + width);
    }
    
    @Override
    public void display() {
        System.out.println("Rectangle: " + length + " x " + width);
        System.out.println("Area: " + calculateArea());
        System.out.println("Perimeter: " + calculatePerimeter());
    }
}

layout: default
#

Default Methods (Java 8+)
#

Default Method Syntax
#

public interface Vehicle {
    // Abstract methods
    void start();
    void stop();
    
    // Default method with implementation
    default void displayInfo() {
        System.out.println("This is a vehicle");
        System.out.println("Brand: " + getBrand());
    }
    
    default String getBrand() {
        return "Generic Vehicle";
    }
    
    // Static method
    static void showVehicleTypes() {
        System.out.println("Types: Car, Bike, Truck");
    }
}

Using Default Methods
#

public class Car implements Vehicle {
    private String model;
    private String brand;
    
    public Car(String brand, String model) {
        this.brand = brand;
        this.model = model;
    }
    
    @Override
    public void start() {
        System.out.println(brand + " " + model + " is starting...");
    }
    
    @Override
    public void stop() {
        System.out.println(brand + " " + model + " has stopped.");
    }
    
    // Override default method (optional)
    @Override
    public String getBrand() {
        return brand;
    }
    
    // Can use default displayInfo() as is
    // or override it if needed
}

Benefits of Default Methods
#

// Usage example
public class VehicleDemo {
    public static void main(String[] args) {
        Car car = new Car("Toyota", "Camry");
        
        // Abstract methods
        car.start();
        car.stop();
        
        // Default method (inherited)
        car.displayInfo();
        
        // Static method
        Vehicle.showVehicleTypes();
    }
}

Output:
#

Toyota Camry is starting...
Toyota Camry has stopped.
This is a vehicle
Brand: Toyota
Types: Car, Bike, Truck

Why Default Methods?
#

  • Backward Compatibility: Add new methods without breaking existing implementations
  • Code Reuse: Provide common implementation for similar behavior
  • Evolution: Allow interfaces to evolve over time
  • Utility Methods: Include helper methods in interfaces

Rules for Default Methods
#

  • Can be overridden in implementing classes
  • Cannot be static or final
  • Cannot override Object class methods
  • Must be declared with default keyword

layout: default
#

Static Methods in Interfaces
#

Static Method Declaration
#

public interface MathOperations {
    // Abstract methods
    double calculate(double a, double b);
    
    // Default method
    default void showResult(double result) {
        System.out.println("Result: " + result);
    }
    
    // Static methods
    static double add(double a, double b) {
        return a + b;
    }
    
    static double multiply(double a, double b) {
        return a * b;
    }
    
    static boolean isPositive(double value) {
        return value > 0;
    }
    
    static void showHelp() {
        System.out.println("Available operations:");
        System.out.println("- Addition");
        System.out.println("- Multiplication");
        System.out.println("- Validation");
    }
}

Implementation and Usage
#

public class Calculator implements MathOperations {
    @Override
    public double calculate(double a, double b) {
        // Using static method from interface
        if (MathOperations.isPositive(a) && 
            MathOperations.isPositive(b)) {
            return MathOperations.multiply(a, b);
        } else {
            return MathOperations.add(a, b);
        }
    }
}

public class MathDemo {
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        
        // Using implemented method
        double result = calc.calculate(5.0, 3.0);
        calc.showResult(result);  // Default method
        
        // Using static methods directly
        double sum = MathOperations.add(10, 20);
        double product = MathOperations.multiply(4, 5);
        
        System.out.println("Sum: " + sum);
        System.out.println("Product: " + product);
        
        MathOperations.showHelp();  // Static method
    }
}

Static Method Characteristics
#

Key Properties
#

  • Belong to the interface, not implementing classes
  • Called using interface name (InterfaceName.methodName)
  • Cannot be overridden in implementing classes
  • Provide utility functionality related to the interface

Utility Interface Example
#

public interface StringUtils {
    // Static utility methods
    static boolean isEmpty(String str) {
        return str == null || str.trim().isEmpty();
    }
    
    static String capitalize(String str) {
        if (isEmpty(str)) return str;
        return str.substring(0, 1).toUpperCase() + 
               str.substring(1).toLowerCase();
    }
    
    static String reverse(String str) {
        if (isEmpty(str)) return str;
        return new StringBuilder(str).reverse().toString();
    }
    
    static int countWords(String str) {
        if (isEmpty(str)) return 0;
        return str.trim().split("\\s+").length;
    }
}

Usage
#

public class StringDemo {
    public static void main(String[] args) {
        String text = "hello world";
        
        System.out.println("Original: " + text);
        System.out.println("Capitalized: " + 
                          StringUtils.capitalize(text));
        System.out.println("Reversed: " + 
                          StringUtils.reverse(text));
        System.out.println("Word count: " + 
                          StringUtils.countWords(text));
        System.out.println("Is empty: " + 
                          StringUtils.isEmpty(text));
    }
}

layout: default
#

Multiple Interface Implementation
#

Multiple Interfaces
#

public interface Flyable {
    void fly();
    
    default void takeOff() {
        System.out.println("Taking off...");
    }
}

public interface Swimmable {
    void swim();
    
    default void dive() {
        System.out.println("Diving...");
    }
}

public interface Walkable {
    void walk();
    
    default void run() {
        System.out.println("Running...");
    }
}

Implementing Multiple Interfaces
#

public class Duck implements Flyable, Swimmable, Walkable {
    private String name;
    
    public Duck(String name) {
        this.name = name;
    }
    
    @Override
    public void fly() {
        System.out.println(name + " is flying in the sky");
    }
    
    @Override
    public void swim() {
        System.out.println(name + " is swimming in the pond");
    }
    
    @Override
    public void walk() {
        System.out.println(name + " is walking on land");
    }
    
    // Can use all default methods
    public void performAllActivities() {
        takeOff();  // From Flyable
        fly();
        dive();     // From Swimmable
        swim();
        walk();
        run();      // From Walkable
    }
}

Diamond Problem Resolution
#

public interface A {
    default void method() {
        System.out.println("A's method");
    }
}

public interface B {
    default void method() {
        System.out.println("B's method");
    }
}

// Compilation error without explicit resolution
public class Diamond implements A, B {
    @Override
    public void method() {
        // Must explicitly choose or provide new implementation
        A.super.method();  // Call A's default method
        // or B.super.method();  // Call B's default method
        // or provide completely new implementation
        System.out.println("Diamond's custom method");
    }
}

Using Multiple Interfaces
#

public class AnimalDemo {
    public static void main(String[] args) {
        Duck duck = new Duck("Donald");
        
        // Duck can be referenced by any of its interfaces
        Flyable flyingDuck = duck;
        Swimmable swimmingDuck = duck;
        Walkable walkingDuck = duck;
        
        flyingDuck.fly();
        swimmingDuck.swim();
        walkingDuck.walk();
        
        // Or use as Duck directly
        duck.performAllActivities();
    }
}

Interface Inheritance
#

public interface Animal {
    void makeSound();
}

public interface Pet extends Animal {
    void play();
    String getName();
}

public class Dog implements Pet {
    private String name;
    
    public Dog(String name) { this.name = name; }
    
    @Override
    public void makeSound() {
        System.out.println(name + " says Woof!");
    }
    
    @Override
    public void play() {
        System.out.println(name + " is playing fetch");
    }
    
    @Override
    public String getName() { return name; }
}

layout: default
#

Functional Interfaces
#

Functional Interface Definition
#

  • Interface with exactly one abstract method
  • Can have default and static methods
  • Annotated with @FunctionalInterface (optional but recommended)
  • Can be used with lambda expressions

Basic Functional Interface
#

@FunctionalInterface
public interface Calculator {
    // Single abstract method (SAM)
    double calculate(double a, double b);
    
    // Default methods allowed
    default void showResult(double result) {
        System.out.println("Result: " + result);
    }
    
    // Static methods allowed
    static void showInfo() {
        System.out.println("Calculator interface");
    }
}

Using with Lambda Expressions
#

public class LambdaDemo {
    public static void main(String[] args) {
        // Lambda expressions
        Calculator add = (a, b) -> a + b;
        Calculator multiply = (a, b) -> a * b;
        Calculator subtract = (a, b) -> a - b;
        
        // Using functional interfaces
        System.out.println("Addition: " + add.calculate(10, 5));
        System.out.println("Multiplication: " + multiply.calculate(10, 5));
        System.out.println("Subtraction: " + subtract.calculate(10, 5));
        
        // Using default method
        add.showResult(add.calculate(15, 25));
    }
}

Built-in Functional Interfaces
#

import java.util.function.*;
import java.util.Arrays;
import java.util.List;

public class BuiltInFunctionalInterfaces {
    public static void main(String[] args) {
        // Predicate<T> - boolean test(T t)
        Predicate<Integer> isEven = num -> num % 2 == 0;
        System.out.println("Is 10 even? " + isEven.test(10));
        
        // Function<T, R> - R apply(T t)
        Function<String, Integer> stringLength = str -> str.length();
        System.out.println("Length of 'Hello': " + 
                          stringLength.apply("Hello"));
        
        // Consumer<T> - void accept(T t)
        Consumer<String> printer = str -> System.out.println(str.toUpperCase());
        printer.accept("hello world");
        
        // Supplier<T> - T get()
        Supplier<Double> randomValue = () -> Math.random();
        System.out.println("Random value: " + randomValue.get());
        
        // Using with collections
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        
        // Filter with Predicate
        numbers.stream()
               .filter(isEven)
               .forEach(System.out::println);
        
        // Transform with Function
        numbers.stream()
               .map(num -> num * num)
               .forEach(System.out::println);
    }
}

Custom Functional Interfaces
#

@FunctionalInterface
public interface StringProcessor {
    String process(String input);
}

@FunctionalInterface
public interface NumberValidator {
    boolean isValid(int number);
}

public class CustomFunctionalDemo {
    public static void main(String[] args) {
        StringProcessor upperCase = str -> str.toUpperCase();
        StringProcessor reverse = str -> new StringBuilder(str).reverse().toString();
        
        NumberValidator positive = num -> num > 0;
        NumberValidator even = num -> num % 2 == 0;
        
        String text = "hello";
        System.out.println(upperCase.process(text));
        System.out.println(reverse.process(text));
        
        int number = 10;
        System.out.println("Is positive: " + positive.isValid(number));
        System.out.println("Is even: " + even.isValid(number));
    }
}

layout: default
#

Practical Example: Media Player System
#

Interface Hierarchy
#

// Base media interface
public interface Media {
    void load();
    void play();
    void stop();
    
    // Default methods
    default void displayInfo() {
        System.out.println("Media Type: " + getType());
        System.out.println("Duration: " + getDuration() + " seconds");
    }
    
    default String getType() {
        return "Generic Media";
    }
    
    default int getDuration() {
        return 0;
    }
    
    // Static utility
    static boolean isValidFormat(String format) {
        return format != null && 
               (format.endsWith(".mp3") || 
                format.endsWith(".mp4") || 
                format.endsWith(".wav"));
    }
}

// Audio-specific interface
public interface AudioMedia extends Media {
    void setVolume(int volume);
    int getVolume();
    
    @Override
    default String getType() {
        return "Audio";
    }
}

// Video-specific interface
public interface VideoMedia extends Media {
    void setResolution(String resolution);
    String getResolution();
    void enableSubtitles(boolean enable);
    
    @Override
    default String getType() {
        return "Video";
    }
}

Implementation Classes
#

// Audio implementation
public class MP3Player implements AudioMedia {
    private String filename;
    private int volume = 50;
    private int duration;
    private boolean isPlaying = false;
    
    public MP3Player(String filename, int duration) {
        this.filename = filename;
        this.duration = duration;
    }
    
    @Override
    public void load() {
        if (Media.isValidFormat(filename)) {
            System.out.println("Loading MP3: " + filename);
        } else {
            throw new IllegalArgumentException("Invalid format");
        }
    }
    
    @Override
    public void play() {
        if (!isPlaying) {
            isPlaying = true;
            System.out.println("Playing " + filename + 
                             " at volume " + volume);
        }
    }
    
    @Override
    public void stop() {
        if (isPlaying) {
            isPlaying = false;
            System.out.println("Stopped " + filename);
        }
    }
    
    @Override
    public void setVolume(int volume) {
        this.volume = Math.max(0, Math.min(100, volume));
    }
    
    @Override
    public int getVolume() { return volume; }
    
    @Override
    public int getDuration() { return duration; }
}

// Video implementation
public class MP4Player implements VideoMedia {
    private String filename;
    private String resolution = "1080p";
    private boolean subtitlesEnabled = false;
    private int duration;
    
    public MP4Player(String filename, int duration) {
        this.filename = filename;
        this.duration = duration;
    }
    
    @Override
    public void load() {
        System.out.println("Loading MP4: " + filename);
    }
    
    @Override
    public void play() {
        System.out.println("Playing " + filename + 
                         " in " + resolution +
                         (subtitlesEnabled ? " with subtitles" : ""));
    }
    
    @Override
    public void stop() {
        System.out.println("Stopped " + filename);
    }
    
    @Override
    public void setResolution(String resolution) {
        this.resolution = resolution;
    }
    
    @Override
    public String getResolution() { return resolution; }
    
    @Override
    public void enableSubtitles(boolean enable) {
        this.subtitlesEnabled = enable;
    }
    
    @Override
    public int getDuration() { return duration; }
}

layout: default
#

Hands-on Exercise 1: E-commerce Payment System
#

Task
#

Design a payment processing system using interfaces:

Payment Interfaces
#

// Base payment interface
public interface PaymentProcessor {
    boolean processPayment(double amount);
    String getTransactionId();
    
    default void generateReceipt(double amount) {
        System.out.println("=== RECEIPT ===");
        System.out.println("Amount: $" + amount);
        System.out.println("Transaction ID: " + getTransactionId());
        System.out.println("Payment Method: " + getPaymentMethod());
        System.out.println("Status: " + 
                         (processPayment(amount) ? "SUCCESS" : "FAILED"));
    }
    
    default String getPaymentMethod() {
        return "Generic Payment";
    }
    
    static boolean isValidAmount(double amount) {
        return amount > 0 && amount <= 10000;
    }
}

// Refundable payment interface
public interface Refundable {
    boolean refund(String transactionId, double amount);
    
    default void processRefund(String transactionId, double amount) {
        if (refund(transactionId, amount)) {
            System.out.println("Refund processed: $" + amount);
        } else {
            System.out.println("Refund failed");
        }
    }
}

// Recurring payment interface
public interface RecurringPayment {
    void setupRecurring(int days);
    void cancelRecurring();
    boolean isRecurringActive();
    
    default void displayRecurringInfo() {
        System.out.println("Recurring payment: " + 
                         (isRecurringActive() ? "Active" : "Inactive"));
    }
}

Payment Implementation
#

// Credit Card payment
public class CreditCardProcessor 
    implements PaymentProcessor, Refundable, RecurringPayment {
    
    private String cardNumber;
    private String transactionId;
    private boolean recurringActive = false;
    
    public CreditCardProcessor(String cardNumber) {
        this.cardNumber = cardNumber;
        this.transactionId = "CC" + System.currentTimeMillis();
    }
    
    @Override
    public boolean processPayment(double amount) {
        if (PaymentProcessor.isValidAmount(amount)) {
            System.out.println("Processing credit card payment: $" + amount);
            // Simulate payment processing
            return true;
        }
        return false;
    }
    
    @Override
    public String getTransactionId() { return transactionId; }
    
    @Override
    public String getPaymentMethod() { return "Credit Card"; }
    
    @Override
    public boolean refund(String transactionId, double amount) {
        if (this.transactionId.equals(transactionId)) {
            System.out.println("Credit card refund: $" + amount);
            return true;
        }
        return false;
    }
    
    @Override
    public void setupRecurring(int days) {
        this.recurringActive = true;
        System.out.println("Recurring payment setup every " + days + " days");
    }
    
    @Override
    public void cancelRecurring() {
        this.recurringActive = false;
        System.out.println("Recurring payment cancelled");
    }
    
    @Override
    public boolean isRecurringActive() { return recurringActive; }
}

// PayPal payment
public class PayPalProcessor implements PaymentProcessor, Refundable {
    private String email;
    private String transactionId;
    
    public PayPalProcessor(String email) {
        this.email = email;
        this.transactionId = "PP" + System.currentTimeMillis();
    }
    
    @Override
    public boolean processPayment(double amount) {
        if (PaymentProcessor.isValidAmount(amount)) {
            System.out.println("Processing PayPal payment: $" + amount);
            return true;
        }
        return false;
    }
    
    @Override
    public String getTransactionId() { return transactionId; }
    
    @Override
    public String getPaymentMethod() { return "PayPal"; }
    
    @Override
    public boolean refund(String transactionId, double amount) {
        if (this.transactionId.equals(transactionId)) {
            System.out.println("PayPal refund: $" + amount);
            return true;
        }
        return false;
    }
}

layout: default
#

Hands-on Exercise 2: Shape Drawing System
#

Drawing System Interfaces
#

// Base drawable interface
@FunctionalInterface
public interface Drawable {
    void draw();
    
    static void drawBorder() {
        System.out.println("==================");
    }
}

// Shape interface extending drawable
public interface Shape extends Drawable {
    double calculateArea();
    double calculatePerimeter();
    
    default void displayDetails() {
        Drawable.drawBorder();
        System.out.println("Shape: " + getShapeType());
        System.out.println("Area: " + calculateArea());
        System.out.println("Perimeter: " + calculatePerimeter());
        draw();
        Drawable.drawBorder();
    }
    
    default String getShapeType() {
        return "Generic Shape";
    }
}

// Colorable interface
public interface Colorable {
    void setColor(String color);
    String getColor();
    
    default void displayColor() {
        System.out.println("Color: " + getColor());
    }
}

// Resizable interface
public interface Resizable {
    void resize(double factor);
    
    default void doubleSize() {
        resize(2.0);
    }
    
    default void halveSize() {
        resize(0.5);
    }
}

Shape Implementations
#

// Circle implementation
public class Circle implements Shape, Colorable, Resizable {
    private double radius;
    private String color = "BLACK";
    
    public Circle(double radius) {
        this.radius = radius;
    }
    
    @Override
    public void draw() {
        System.out.println("Drawing a circle with radius: " + radius);
    }
    
    @Override
    public double calculateArea() {
        return Math.PI * radius * radius;
    }
    
    @Override
    public double calculatePerimeter() {
        return 2 * Math.PI * radius;
    }
    
    @Override
    public String getShapeType() { return "Circle"; }
    
    @Override
    public void setColor(String color) { this.color = color; }
    
    @Override
    public String getColor() { return color; }
    
    @Override
    public void resize(double factor) {
        if (factor > 0) {
            radius *= factor;
            System.out.println("Circle resized by factor: " + factor);
        }
    }
}

// Rectangle implementation
public class Rectangle implements Shape, Colorable, Resizable {
    private double length, width;
    private String color = "BLACK";
    
    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }
    
    @Override
    public void draw() {
        System.out.println("Drawing rectangle: " + length + " x " + width);
    }
    
    @Override
    public double calculateArea() {
        return length * width;
    }
    
    @Override
    public double calculatePerimeter() {
        return 2 * (length + width);
    }
    
    @Override
    public String getShapeType() { return "Rectangle"; }
    
    @Override
    public void setColor(String color) { this.color = color; }
    
    @Override
    public String getColor() { return color; }
    
    @Override
    public void resize(double factor) {
        if (factor > 0) {
            length *= factor;
            width *= factor;
            System.out.println("Rectangle resized by factor: " + factor);
        }
    }
}

Usage Example
#

public class ShapeDemo {
    public static void main(String[] args) {
        Shape circle = new Circle(5.0);
        Shape rectangle = new Rectangle(4.0, 6.0);
        
        // Use as Shape
        circle.displayDetails();
        rectangle.displayDetails();
        
        // Use as Colorable
        ((Colorable) circle).setColor("RED");
        ((Colorable) rectangle).setColor("BLUE");
        
        // Use as Resizable
        ((Resizable) circle).doubleSize();
        ((Resizable) rectangle).halveSize();
        
        circle.displayDetails();
    }
}

layout: default
#

Interface Design Patterns
#

Strategy Pattern
#

// Strategy interface
@FunctionalInterface
public interface SortingStrategy {
    void sort(int[] array);
}

// Context class
public class Sorter {
    private SortingStrategy strategy;
    
    public Sorter(SortingStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void setStrategy(SortingStrategy strategy) {
        this.strategy = strategy;
    }
    
    public void performSort(int[] array) {
        strategy.sort(array);
    }
}

// Usage with lambdas
public class StrategyDemo {
    public static void main(String[] args) {
        int[] data = {5, 2, 8, 1, 9};
        
        Sorter sorter = new Sorter(Arrays::sort); // Method reference
        
        // Bubble sort strategy
        sorter.setStrategy(array -> {
            for (int i = 0; i < array.length - 1; i++) {
                for (int j = 0; j < array.length - i - 1; j++) {
                    if (array[j] > array[j + 1]) {
                        int temp = array[j];
                        array[j] = array[j + 1];
                        array[j + 1] = temp;
                    }
                }
            }
        });
        
        sorter.performSort(data);
        System.out.println(Arrays.toString(data));
    }
}

Observer Pattern
#

// Observer interface
@FunctionalInterface
public interface Observer {
    void update(String message);
}

// Subject interface
public interface Subject {
    void attach(Observer observer);
    void detach(Observer observer);
    void notifyObservers(String message);
}

// Concrete subject
public class NewsAgency implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private String news;
    
    @Override
    public void attach(Observer observer) {
        observers.add(observer);
    }
    
    @Override
    public void detach(Observer observer) {
        observers.remove(observer);
    }
    
    @Override
    public void notifyObservers(String message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
    
    public void setNews(String news) {
        this.news = news;
        notifyObservers("Breaking News: " + news);
    }
}

// Usage
public class ObserverDemo {
    public static void main(String[] args) {
        NewsAgency agency = new NewsAgency();
        
        // Lambda observers
        Observer tvChannel = msg -> System.out.println("TV: " + msg);
        Observer newspaper = msg -> System.out.println("Print: " + msg);
        Observer website = msg -> System.out.println("Web: " + msg);
        
        agency.attach(tvChannel);
        agency.attach(newspaper);
        agency.attach(website);
        
        agency.setNews("New technology breakthrough!");
    }
}

Factory Pattern
#

// Product interface
public interface Animal {
    void makeSound();
    String getType();
}

// Factory interface
@FunctionalInterface
public interface AnimalFactory {
    Animal createAnimal(String name);
}

// Usage
public class FactoryDemo {
    public static void main(String[] args) {
        Map<String, AnimalFactory> factories = Map.of(
            "dog", name -> new Dog(name),
            "cat", name -> new Cat(name),
            "bird", name -> new Bird(name)
        );
        
        Animal dog = factories.get("dog").createAnimal("Buddy");
        dog.makeSound();
    }
}

layout: default
#

Interface Best Practices
#

Design Principles
#

1. Interface Segregation Principle
#

// BAD: Fat interface
public interface Worker {
    void work();
    void eat();
    void sleep();
    void program();
    void managePeople();
}

// GOOD: Segregated interfaces
public interface Workable {
    void work();
}

public interface Eatable {
    void eat();
}

public interface Sleepable {
    void sleep();
}

public interface Programmable {
    void program();
}

public interface Manageable {
    void managePeople();
}

// Classes implement only what they need
public class Programmer implements Workable, Programmable, Eatable {
    @Override
    public void work() { /* implementation */ }
    
    @Override
    public void program() { /* implementation */ }
    
    @Override
    public void eat() { /* implementation */ }
}

2. Dependency Inversion Principle
#

// BAD: Depend on concrete class
public class EmailService {
    private SMTPMailer mailer; // Concrete dependency
    
    public void sendEmail(String message) {
        mailer.send(message);
    }
}

// GOOD: Depend on abstraction
public interface MailSender {
    void send(String message);
}

public class EmailService {
    private MailSender mailer; // Interface dependency
    
    public EmailService(MailSender mailer) {
        this.mailer = mailer;
    }
    
    public void sendEmail(String message) {
        mailer.send(message);
    }
}

Naming Conventions
#

Interface Naming
#

// Good naming patterns
public interface Readable { }          // -able suffix
public interface Comparable<T> { }     // -able suffix
public interface Runnable { }          // -able suffix

public interface PaymentProcessor { }   // Function-based naming
public interface DataValidator { }      // Function-based naming
public interface MessageHandler { }     // Function-based naming

// Avoid 'I' prefix (C# convention, not Java)
// BAD: IPaymentProcessor
// GOOD: PaymentProcessor

Documentation Best Practices
#

/**
 * Represents a data source that can be queried and updated.
 * 
 * <p>Implementations should ensure thread safety if the data source
 * will be accessed from multiple threads concurrently.</p>
 * 
 * @param <T> the type of data stored in this source
 * @since 1.0
 */
public interface DataSource<T> {
    /**
     * Retrieves data by the specified key.
     * 
     * @param key the unique identifier for the data
     * @return the data associated with the key, or null if not found
     * @throws IllegalArgumentException if key is null
     */
    T getData(String key);
    
    /**
     * Stores data with the specified key.
     * 
     * @param key the unique identifier for the data
     * @param data the data to store
     * @throws IllegalArgumentException if key or data is null
     */
    void setData(String key, T data);
    
    /**
     * Checks if data exists for the specified key.
     * 
     * @param key the key to check
     * @return true if data exists, false otherwise
     */
    default boolean hasData(String key) {
        return getData(key) != null;
    }
}

Performance Considerations
#

public interface CacheManager<K, V> {
    V get(K key);
    void put(K key, V value);
    
    // Default method with efficient implementation
    default void putAll(Map<K, V> data) {
        // Bulk operation for better performance
        data.forEach(this::put);
    }
    
    // Method to get statistics
    default CacheStats getStats() {
        return CacheStats.empty(); // Default implementation
    }
}

layout: default
#

Common Interface Pitfalls
#

Pitfall 1: Overusing Interfaces
#

// BAD: Unnecessary interface for simple value object
public interface PersonData {
    String getName();
    int getAge();
    void setName(String name);
    void setAge(int age);
}

// GOOD: Simple class for data
public class Person {
    private String name;
    private int age;
    
    // constructors, getters, setters
}

Pitfall 2: Fat Interfaces
#

// BAD: Interface doing too many things
public interface DatabaseManager {
    void connect();
    void disconnect();
    void createTable(String name);
    void dropTable(String name);
    List<String> executeQuery(String sql);
    void executeUpdate(String sql);
    void backup();
    void restore();
    void optimize();
}

// GOOD: Separated concerns
public interface ConnectionManager {
    void connect();
    void disconnect();
}

public interface QueryExecutor {
    List<String> executeQuery(String sql);
    void executeUpdate(String sql);
}

public interface DatabaseMaintenance {
    void backup();
    void restore();
    void optimize();
}

Pitfall 3: Exposing Implementation Details
#

// BAD: Interface exposing internal structures
public interface UserManager {
    HashMap<String, User> getUserMap(); // Exposes internal structure
    ArrayList<User> getUserList();      // Exposes internal structure
}

// GOOD: Abstract interface
public interface UserManager {
    User getUser(String id);
    Collection<User> getAllUsers();      // Abstract collection
    boolean addUser(User user);
    boolean removeUser(String id);
}

Pitfall 4: Marker Interfaces Abuse
#

// BAD: Using marker interfaces for configuration
public interface FastProcessing { } // Empty marker
public interface SlowProcessing { } // Empty marker

// GOOD: Use enums or configuration
public enum ProcessingSpeed {
    FAST, NORMAL, SLOW
}

public interface DataProcessor {
    void process(Data data, ProcessingSpeed speed);
}

Pitfall 5: Ignoring Default Method Conflicts
#

// Problematic: Same default method signature
public interface A {
    default String getValue() { return "A"; }
}

public interface B {
    default String getValue() { return "B"; }
}

// GOOD: Explicit resolution
public class Implementation implements A, B {
    @Override
    public String getValue() {
        return A.super.getValue() + " and " + B.super.getValue();
    }
}

layout: default
#

Advanced Interface Concepts
#

Generic Interfaces
#

// Generic interface with type parameters
public interface Repository<T, ID> {
    void save(T entity);
    T findById(ID id);
    List<T> findAll();
    void deleteById(ID id);
    boolean existsById(ID id);
    
    // Default method with generics
    default void saveAll(Collection<T> entities) {
        entities.forEach(this::save);
    }
}

// Specific implementation
public class UserRepository implements Repository<User, Long> {
    private Map<Long, User> users = new HashMap<>();
    
    @Override
    public void save(User user) {
        users.put(user.getId(), user);
    }
    
    @Override
    public User findById(Long id) {
        return users.get(id);
    }
    
    @Override
    public List<User> findAll() {
        return new ArrayList<>(users.values());
    }
    
    @Override
    public void deleteById(Long id) {
        users.remove(id);
    }
    
    @Override
    public boolean existsById(Long id) {
        return users.containsKey(id);
    }
}

Bounded Generic Interfaces
#

// Bounded type parameters
public interface Comparable<T> {
    int compareTo(T other);
}

public interface NumberProcessor<T extends Number> {
    T process(T input);
    
    default double toDouble(T value) {
        return value.doubleValue();
    }
    
    default boolean isPositive(T value) {
        return toDouble(value) > 0;
    }
}

// Implementation with bounded types
public class IntegerProcessor implements NumberProcessor<Integer> {
    @Override
    public Integer process(Integer input) {
        return input * 2;
    }
}

// Wildcard usage
public class ProcessorUtil {
    public static void processNumbers(
        List<? extends Number> numbers,
        NumberProcessor<? super Number> processor) {
        
        for (Number num : numbers) {
            Number result = processor.process(num);
            System.out.println("Processed: " + result);
        }
    }
}

Nested Interfaces
#

public interface OuterInterface {
    void outerMethod();
    
    // Nested interface
    interface NestedInterface {
        void nestedMethod();
        
        // Static nested interface
        static interface StaticNested {
            void staticNestedMethod();
        }
    }
    
    // Default method using nested interface
    default void processWithNested(NestedInterface nested) {
        nested.nestedMethod();
    }
}

// Implementation
public class Implementation implements OuterInterface, 
                                      OuterInterface.NestedInterface {
    @Override
    public void outerMethod() {
        System.out.println("Outer method");
    }
    
    @Override
    public void nestedMethod() {
        System.out.println("Nested method");
    }
}

layout: default
#

Summary and Key Takeaways
#

What We Learned
#

  • ๐ŸŽฏ Interface Fundamentals: Contract definition and implementation
  • ๐Ÿ“‹ Method Types: Abstract, default, static, and private methods
  • ๐Ÿ”ง Multiple Inheritance: Implementing multiple interfaces safely
  • ๐Ÿš€ Functional Interfaces: Single abstract method and lambda support
  • โšก Design Patterns: Strategy, Observer, Factory using interfaces
  • ๐Ÿ“ Best Practices: Interface segregation and dependency inversion
  • ๐Ÿ—๏ธ Advanced Concepts: Generics, bounded types, nested interfaces

Interface Evolution in Java
#

Java 8+
#

  • Default methods
  • Static methods
  • Functional interfaces
  • Lambda expressions

Java 9+
#

  • Private methods in interfaces
  • Private static methods

Best Practices Recap
#

  • Keep interfaces focused and cohesive
  • Use meaningful and consistent naming
  • Prefer composition over inheritance
  • Document interface contracts clearly
  • Use default methods for backward compatibility
  • Avoid exposing implementation details
  • Consider generic interfaces for reusability
  • Test interface implementations thoroughly

Next Lecture Preview
#

Lecture 24: Interface Implementation

  • Multiple interface implementation
  • Interface relationships and hierarchies
  • Resolving method conflicts
  • Advanced implementation patterns

layout: center class: text-center
#

Thank You!
#

Questions and Discussion
#

Next Lecture: Interface Implementation
Topic: Multiple inheritance, interface relationships, advanced patterns

Practice interface design and implementation!