Skip to content

Interfaces and Abstract Classes in Java

Question

Compare and contrast interfaces and abstract classes in Java. Explain their purposes, implementation requirements, and use cases. When would you choose one over the other?

Answer

Interfaces and abstract classes are both mechanisms for achieving abstraction in Java, but they serve different purposes and have distinct characteristics.

Abstract Classes

  1. Basic Abstract Class

    public abstract class Shape {
        protected double area;
        protected String color;
    
        public Shape(String color) {
            this.color = color;
        }
    
        // Abstract method
        public abstract double calculateArea();
    
        // Concrete method
        public void displayInfo() {
            System.out.println("Color: " + color);
            System.out.println("Area: " + area);
        }
    }
    
    public class Circle extends Shape {
        private double radius;
    
        public Circle(String color, double radius) {
            super(color);
            this.radius = radius;
        }
    
        @Override
        public double calculateArea() {
            return Math.PI * radius * radius;
        }
    }
    

  2. Partial Implementation

    public abstract class Database {
        protected Connection connection;
    
        public void connect() {
            // Common connection logic
            connection = establishConnection();
        }
    
        protected abstract Connection establishConnection();
    
        public abstract void disconnect();
    }
    
    public class MySQLDatabase extends Database {
        @Override
        protected Connection establishConnection() {
            // MySQL-specific connection
            return DriverManager.getConnection("jdbc:mysql://...");
        }
    
        @Override
        public void disconnect() {
            // MySQL-specific disconnection
            connection.close();
        }
    }
    

Interfaces

  1. Basic Interface

    public interface Drawable {
        void draw();
        double getArea();
        void setColor(String color);
    }
    
    public class Rectangle implements Drawable {
        private double width;
        private double height;
        private String color;
    
        @Override
        public void draw() {
            System.out.println("Drawing rectangle");
        }
    
        @Override
        public double getArea() {
            return width * height;
        }
    
        @Override
        public void setColor(String color) {
            this.color = color;
        }
    }
    

  2. Multiple Interface Implementation

    public interface Flyable {
        void fly();
    }
    
    public interface Swimmable {
        void swim();
    }
    
    public class Duck implements Flyable, Swimmable {
        @Override
        public void fly() {
            System.out.println("Duck is flying");
        }
    
        @Override
        public void swim() {
            System.out.println("Duck is swimming");
        }
    }
    

Key Differences

  1. Constructor Support

    // Abstract class can have constructors
    public abstract class Vehicle {
        protected String model;
    
        public Vehicle(String model) {
            this.model = model;
        }
    }
    
    // Interface cannot have constructors
    public interface Vehicle {
        // No constructors allowed
    }
    

  2. Method Implementation

    // Abstract class can have both abstract and concrete methods
    public abstract class PaymentProcessor {
        public abstract void processPayment(double amount);
    
        public void validateAmount(double amount) {
            if (amount <= 0) {
                throw new IllegalArgumentException("Invalid amount");
            }
        }
    }
    
    // Interface methods are implicitly abstract (except default methods)
    public interface PaymentProcessor {
        void processPayment(double amount);
        // Cannot have concrete methods without 'default' keyword
    }
    

Modern Java Features

  1. Default Methods in Interfaces

    public interface Logger {
        void log(String message);
    
        default void logError(String message) {
            log("ERROR: " + message);
        }
    
        default void logInfo(String message) {
            log("INFO: " + message);
        }
    }
    

  2. Private Methods in Interfaces

    public interface StringProcessor {
        default String process(String input) {
            return format(validate(input));
        }
    
        private String validate(String input) {
            if (input == null || input.trim().isEmpty()) {
                throw new IllegalArgumentException("Invalid input");
            }
            return input.trim();
        }
    
        private String format(String input) {
            return input.toUpperCase();
        }
    }
    

Best Practices

  1. Choosing Between Interface and Abstract Class

    // Use abstract class when you have common implementation
    public abstract class Database {
        protected Connection connection;
    
        public void connect() {
            // Common connection logic
        }
    
        protected abstract void configureConnection();
    }
    
    // Use interface when you need to define a contract
    public interface PaymentMethod {
        void processPayment(double amount);
        boolean validatePayment();
        String getPaymentDetails();
    }
    

  2. Interface Segregation

    // Bad - Too many methods in one interface
    public interface Worker {
        void work();
        void eat();
        void sleep();
        void commute();
    }
    
    // Good - Segregated interfaces
    public interface Workable {
        void work();
    }
    
    public interface Eatable {
        void eat();
    }
    
    public interface Sleepable {
        void sleep();
    }
    
    public class Employee implements Workable, Eatable, Sleepable {
        // Implement only needed methods
    }
    

Common Use Cases

  1. Template Method Pattern

    public abstract class Game {
        public final void play() {
            initialize();
            startGame();
            endGame();
        }
    
        protected abstract void initialize();
        protected abstract void startGame();
        protected abstract void endGame();
    }
    
    public class Chess extends Game {
        @Override
        protected void initialize() {
            // Chess-specific initialization
        }
    
        @Override
        protected void startGame() {
            // Chess-specific game start
        }
    
        @Override
        protected void endGame() {
            // Chess-specific game end
        }
    }
    

  2. Strategy Pattern

    public interface PaymentStrategy {
        void pay(double amount);
    }
    
    public class CreditCardPayment implements PaymentStrategy {
        @Override
        public void pay(double amount) {
            // Credit card payment implementation
        }
    }
    
    public class PayPalPayment implements PaymentStrategy {
        @Override
        public void pay(double amount) {
            // PayPal payment implementation
        }
    }
    

Testing

  1. Testing Abstract Classes

    @Test
    public void testShape() {
        Shape circle = new Circle("red", 5.0);
        circle.calculateArea();
        circle.displayInfo();
    
        assertEquals("red", circle.color);
        assertTrue(circle.area > 0);
    }
    

  2. Testing Interfaces

    @Test
    public void testDrawable() {
        Drawable rectangle = new Rectangle(10, 20);
        rectangle.setColor("blue");
        rectangle.draw();
    
        assertEquals(200.0, rectangle.getArea());
    }