Skip to content

Interface vs Abstract Class

Interfaces and abstract classes are both used for abstraction in Java, but they serve different purposes and have different constraints. Understanding when to use each is critical for good design.


Quick Comparison

Feature Interface Abstract Class
Inheritance A class can implement multiple interfaces A class can extend only one abstract class
Methods Default: public abstract; can have default/static/private methods (Java 8+) Can have abstract and concrete methods
State Can't have instance variables (only constants) Can have instance variables (any visibility)
Constructor Can't have constructors Can have constructors (to initialize fields)
Access Modifiers Methods are public; fields are public static final Can use any modifier (public, private, protected)
When to use Define a contract/capability Share code and state across related classes
Relationship "Can do" (capability) "Is a" (inheritance hierarchy)

Detailed Comparison

Interfaces: Defining Contracts

An interface defines what a class should do, not how it does it. It's a contract.

// Interface: defines capability
public interface PaymentProcessor {

    // Abstract method (must be implemented)
    PaymentResponse processPayment(Order order);

    // Default method (optional override)
    default void logTransaction(Order order) {
        System.out.println("Processed: " + order.getId());
    }

    // Static method (utility)
    static PaymentProcessor getDefault() {
        return new CreditCardProcessor();
    }

    // Constant (public static final)
    String CURRENCY = "USD";
}

// Multiple implementations
public class CreditCardProcessor implements PaymentProcessor {
    @Override
    public PaymentResponse processPayment(Order order) {
        // Credit card logic
        return new PaymentResponse("SUCCESS");
    }
}

public class PayPalProcessor implements PaymentProcessor {
    @Override
    public PaymentResponse processPayment(Order order) {
        // PayPal logic
        return new PaymentResponse("SUCCESS");
    }
}

// A class can implement multiple interfaces
public class HybridProcessor implements PaymentProcessor, RefundProcessor, NotificationProcessor {
    // Must implement all three interfaces
}

Key Points: - ✅ Multiple implementation (polymorphism) - ✅ Loose coupling (depends on interface, not concrete class) - ✅ Mixin behavior (class can be many things) - ❌ Can't share state (only constants)


Abstract Classes: Sharing Implementation

An abstract class defines what subclasses should be and provides shared code/state.

// Abstract class: defines hierarchy and shared behavior
public abstract class Animal {

    // Shared state (instance variable)
    private String name;
    private int age;

    // Constructor to initialize state
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Abstract method (subclasses must implement)
    public abstract void makeSound();

    // Concrete method (shared by all subclasses)
    public void sleep() {
        System.out.println(name + " is sleeping");
    }

    // Getter (access to shared state)
    public String getName() {
        return name;
    }
}

// Single inheritance
public class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);  // Initialize parent state
    }

    @Override
    public void makeSound() {
        System.out.println("Bark");
    }
}

// ❌ This won't work (can't extend multiple classes)
public class Hybrid extends Dog, Cat { }  // ERROR

Key Points: - ✅ Share state and behavior - ✅ Constructor to initialize shared state - ✅ Protects internal implementation - ❌ Single inheritance only - ❌ Tighter coupling between parent and child


When to Use Each

Use Interface When:

// 1. Define a capability/behavior unrelated to class hierarchy
public interface Serializable {
    byte[] toBytes();
    void fromBytes(byte[] data);
}

// Any class can implement it (String, List, Order, etc.)
public class Order implements Serializable {
    // ...
}

public class User implements Serializable {
    // ...
}

// 2. Multiple inheritance of behavior
public class PaymentService implements PaymentProcessor, AuditLogger, NotificationSender {
    // Does multiple things
}

// 3. Strategy pattern (plug-and-play implementations)
public interface ReportGenerator {
    Report generate(Data data);
}

public class PdfReportGenerator implements ReportGenerator { }
public class ExcelReportGenerator implements ReportGenerator { }
public class HtmlReportGenerator implements ReportGenerator { }

// Client doesn't care which implementation
public class ReportingService {
    private ReportGenerator generator;  // Can be any implementation

    public Report buildReport(Data data) {
        return generator.generate(data);  // Polymorphic call
    }
}

Use Abstract Class When:

// 1. Share code and state across related classes
public abstract class Vehicle {
    private int speed;  // Shared state
    private String color;

    public abstract void accelerate();

    public void stop() {
        speed = 0;  // Shared behavior using shared state
        System.out.println("Stopped");
    }
}

public class Car extends Vehicle {
    @Override
    public void accelerate() {
        speed += 20;
    }
}

// 2. Define a class hierarchy with "is-a" relationship
public abstract class Shape {
    public abstract double getArea();
    public abstract double getPerimeter();
}

public class Circle extends Shape {
    private double radius;

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

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

// 3. Enforce consistent initialization
public abstract class Database {
    protected Connection conn;

    public Database(String url) {
        // Guaranteed initialization path
        this.conn = DriverManager.getConnection(url);
        initialize();
    }

    protected abstract void initialize();
}

public class MySQLDatabase extends Database {
    public MySQLDatabase(String url) {
        super(url);  // Parent constructor ensures setup
    }

    @Override
    protected void initialize() {
        // MySQL-specific init
    }
}

// 4. Access control (protected methods)
public abstract class BaseService {

    protected void log(String msg) {
        System.out.println(msg);  // Subclasses can call
    }

    public abstract void execute();
}

public class MyService extends BaseService {
    @Override
    public void execute() {
        log("Starting...");  // Can use protected method
    }
}

Real-World Scenarios

Scenario 1: Payment Processing

// ✅ Use Interface: Different payment methods (credit card, PayPal, crypto)
public interface PaymentMethod {
    PaymentResponse pay(Order order);
}

public class CreditCardPayment implements PaymentMethod { }
public class PayPalPayment implements PaymentMethod { }
public class CryptoPayment implements PaymentMethod { }

// Multiple implementations, no shared state

Scenario 2: Data Access

// ✅ Use Abstract Class: Shared connection logic, different databases
public abstract class BaseRepository {
    protected Connection conn;  // Shared state

    public BaseRepository(String url) {
        this.conn = connect(url);  // Shared initialization
    }

    protected abstract List<?> query(String sql);
}

public class UserRepository extends BaseRepository {
    public UserRepository(String url) {
        super(url);
    }

    @Override
    protected List<?> query(String sql) {
        // MySQL-specific querying
    }
}

public class OrderRepository extends BaseRepository {
    // Shares connection logic with UserRepository
}

Scenario 3: Plugin System

// ✅ Use Interface: Plugins should be loosely coupled
public interface Plugin {
    void onLoad();
    void onEvent(Event e);
    void onUnload();
}

public class AuthPlugin implements Plugin { }
public class CachePlugin implements Plugin { }
public class MonitoringPlugin implements Plugin { }

// Main app doesn't care about plugin internals
public class PluginManager {
    private List<Plugin> plugins = new ArrayList<>();

    public void loadPlugins(String path) {
        // Discover and load any Plugin implementation
    }
}

Scenario 4: Template Method Pattern

// ✅ Use Abstract Class: Define algorithm skeleton, let subclasses fill in steps
public abstract class DataProcessor {

    public final void process(Data data) {
        // Template: algorithm skeleton (final prevents override)
        validate(data);
        transform(data);
        save(data);
        notifyCompletion(data);
    }

    protected abstract void validate(Data data);
    protected abstract void transform(Data data);
    protected abstract void save(Data data);

    protected void notifyCompletion(Data data) {
        System.out.println("Done: " + data.getId());
    }
}

public class ReportProcessor extends DataProcessor {
    @Override
    protected void validate(Data data) { }

    @Override
    protected void transform(Data data) { }

    @Override
    protected void save(Data data) { }
}

Java 8+ Default Methods: Blurring the Line

Java 8 introduced default methods in interfaces, making them more like abstract classes:

// Modern interface with default implementation
public interface Repository<T> {

    // Abstract: subclasses must implement
    T findById(Long id);

    // Default: subclasses can override, but don't have to
    default List<T> findAll() {
        return Collections.emptyList();
    }

    // Static utility method
    static <T> Repository<T> getEmpty() {
        return new Repository<T>() {
            @Override
            public T findById(Long id) { return null; }
        };
    }
}

// Subclass can skip implementing findAll() if not needed
public class UserRepository implements Repository<User> {
    @Override
    public User findById(Long id) { }

    // Inherits default findAll()
}

This doesn't make interfaces obsolete: - Interfaces still can't have state (instance variables) - Interfaces still express "can do" (capability) - Abstract classes still express "is a" (hierarchy)


Decision Tree

                ┌─ Do you need shared STATE?
                │  (instance variables)
                │  ├─ YES → Abstract Class
                │  └─ NO → Could be interface
                │
     Problem    ├─ Do you need CONSTRUCTORS?
       Design   │  (to initialize objects)
         ?      │  ├─ YES → Abstract Class
                │  └─ NO → Could be interface
                │
                ├─ Do you need SINGLE inheritance?
                │  (only one parent)
                │  ├─ YES → Could be either
                │  └─ NO (multiple parents) → Interface
                │
                └─ Is this a CLASS HIERARCHY?
                   (X is-a Y)
                   ├─ YES → Abstract Class
                   └─ NO (just capabilities) → Interface

Interview Talking Points

  • Key difference? Interfaces define "what can be done"; abstract classes define "what things are"
  • State? Interfaces have no instance variables; abstract classes do
  • Inheritance? Multiple interfaces, single abstract class (per class)
  • Constructors? Abstract classes have them (initialize state); interfaces don't
  • When to use which? State + hierarchy = abstract class; capability + multiple types = interface
  • Default methods? Java 8+ interfaces can have default implementations, but still no state
  • Mixin behavior? Use interfaces; abstract classes are for inheritance hierarchies
  • Template method? Abstract classes (define skeleton); interfaces just define contract

See also: OOP Concepts, Design Patterns