foreach-ui logo
codeLanguages
account_treeDSA

Quick Actions

quizlock Random Quiz
trending_uplock Progress
  • 1
  • 2
  • 3
  • 4
  • 5
  • quiz
Java
  • Understand the purpose and use of abstract classes
  • Master interface declaration and implementation
  • Learn when to use abstract classes vs interfaces

Abstract Classes and Interfaces in Java

As programs grow, classes share common characteristics. Dogs, cats, birds—all animals. Cars, motorcycles, bicycles—all vehicles. How do you model these relationships in a flexible, type-safe way?

Abstract classes and interfaces both let you define contracts that other classes follow. But they serve different purposes. Knowing when to use each is key.

The Need for Abstraction

Take a drawing app. You have shapes: circles, rectangles, triangles. Each needs to be drawn and calculate its area. Without abstraction:

class Circle {
    void draw() { /* draw circle */ }
    double area() { /* calculate circle area */ }
}

class Rectangle {
    void draw() { /* draw rectangle */ }
    double area() { /* calculate rectangle area */ }
}

But how do you store different shapes together? How do you ensure every new shape implements draw() and area()? Abstraction solves both problems.

Abstract Classes

An abstract class is a class that cannot be instantiated directly. It serves as a blueprint for other classes, potentially containing both implemented and unimplemented methods.

When to Use Abstract Classes

Use an abstract class when:

  • You want to share code among closely related classes
  • You expect subclasses to have common fields or methods
  • You want to declare non-public members (protected, package-private)
  • You need constructors or instance initialization

Declaring an Abstract Class

public abstract class Shape {
    // Instance variables - shared by all shapes
    protected String color;
    protected boolean filled;
    
    // Constructor - called by subclasses
    public Shape(String color, boolean filled) {
        this.color = color;
        this.filled = filled;
    }
    
    // Abstract methods - MUST be implemented by subclasses
    public abstract double area();
    public abstract double perimeter();
    
    // Concrete method - shared implementation
    public void describe() {
        System.out.println("A " + (filled ? "filled " : "") + color + " shape");
    }
    
    // Concrete method with common logic
    public String getColor() {
        return color;
    }
}

Key observations:

  • The class is marked abstract - you cannot write new Shape()
  • Abstract methods have no body - just a signature ending with ;
  • Concrete methods have full implementations that subclasses inherit
  • Abstract classes can have constructors (called via super())

Implementing an Abstract Class

Subclasses must implement all abstract methods (unless they're also abstract):

public class Circle extends Shape {
    private double radius;
    
    public Circle(String color, boolean filled, double radius) {
        super(color, filled);  // Call parent constructor
        this.radius = radius;
    }
    
    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
    
    @Override
    public double perimeter() {
        return 2 * Math.PI * radius;
    }
    
    // Can add additional methods
    public double getRadius() {
        return radius;
    }
}

public class Rectangle extends Shape {
    private double width;
    private double height;
    
    public Rectangle(String color, boolean filled, double width, double height) {
        super(color, filled);
        this.width = width;
        this.height = height;
    }
    
    @Override
    public double area() {
        return width * height;
    }
    
    @Override
    public double perimeter() {
        return 2 * (width + height);
    }
}

Using Abstract Classes Polymorphically

The power comes from treating different shapes uniformly:

// Can store any Shape subclass
Shape[] shapes = {
    new Circle("red", true, 5.0),
    new Rectangle("blue", false, 4.0, 3.0),
    new Circle("green", true, 2.5)
};

// Process them uniformly
double totalArea = 0;
for (Shape shape : shapes) {
    shape.describe();         // Inherited method
    totalArea += shape.area(); // Polymorphic call
}
System.out.println("Total area: " + totalArea);

Interfaces

An interface is a completely abstract type that defines a contract. It specifies what a class can do, without dictating how it does it.

When to Use Interfaces

Use an interface when:

  • You want unrelated classes to share a capability
  • You need multiple inheritance of type
  • You want to define a contract without implementation
  • You're designing for flexibility and loose coupling

Declaring an Interface

public interface Drawable {
    // All methods are implicitly public and abstract
    void draw();
    
    // Constants are implicitly public, static, and final
    int DEFAULT_LINE_WIDTH = 1;
}

Before Java 8, interfaces could only contain abstract methods. Now they can also have:

public interface Drawable {
    // Abstract method (must be implemented)
    void draw();
    
    // Default method (has implementation, can be overridden)
    default void drawWithBorder() {
        System.out.println("Drawing border...");
        draw();  // Calls the abstract method
    }
    
    // Static method (belongs to the interface itself)
    static Drawable empty() {
        return () -> { };  // Do-nothing drawable
    }
    
    // Private method (Java 9+, helper for default methods)
    private void logDrawing() {
        System.out.println("Drawing operation performed");
    }
}

Implementing Interfaces

A class implements an interface using the implements keyword:

public class Circle extends Shape implements Drawable {
    // ... Shape implementation ...
    
    @Override
    public void draw() {
        System.out.println("Drawing a " + color + " circle with radius " + radius);
    }
}

Multiple Interface Implementation

Unlike classes (single inheritance), a class can implement multiple interfaces:

public interface Printable {
    void print();
}

public interface Saveable {
    void save(String filename);
}

// A class can implement multiple interfaces
public class Document implements Printable, Saveable, Drawable {
    @Override
    public void print() {
        System.out.println("Printing document...");
    }
    
    @Override
    public void save(String filename) {
        System.out.println("Saving to " + filename);
    }
    
    @Override
    public void draw() {
        System.out.println("Drawing document preview...");
    }
}

This enables "mix-in" style programming where classes gain capabilities by implementing interfaces.

Abstract Classes vs Interfaces: Making the Choice

Feature Abstract Class Interface
Multiple inheritance No (single parent) Yes (many interfaces)
Instance variables Yes No (only constants)
Constructors Yes No
Access modifiers Any Public only (mostly)
Method implementations Any (abstract or concrete) Default/static (or abstract)
Use case IS-A relationship with shared code CAN-DO capability

The IS-A vs CAN-DO Rule

Abstract class: Use when subclass "IS-A" type of the parent

  • Dog IS-A Animal
  • Circle IS-A Shape

Interface: Use when class "CAN-DO" something

  • Dog CAN-DO Runnable
  • Circle CAN-DO Drawable

A Practical Example

// Abstract class for shared code and IS-A relationship
public abstract class Animal {
    protected String name;
    
    public Animal(String name) {
        this.name = name;
    }
    
    public abstract void makeSound();
    
    public void sleep() {
        System.out.println(name + " is sleeping");
    }
}

// Interfaces for CAN-DO capabilities
public interface Swimmable {
    void swim();
}

public interface Flyable {
    void fly();
}

// A duck IS-A animal that CAN swim and CAN fly
public class Duck extends Animal implements Swimmable, Flyable {
    public Duck(String name) {
        super(name);
    }
    
    @Override
    public void makeSound() {
        System.out.println(name + " says: Quack!");
    }
    
    @Override
    public void swim() {
        System.out.println(name + " is swimming");
    }
    
    @Override
    public void fly() {
        System.out.println(name + " is flying");
    }
}

// A fish IS-A animal that CAN swim (but not fly)
public class Fish extends Animal implements Swimmable {
    public Fish(String name) {
        super(name);
    }
    
    @Override
    public void makeSound() {
        System.out.println(name + " says: Blub!");
    }
    
    @Override
    public void swim() {
        System.out.println(name + " is swimming");
    }
}

Interface Evolution: Default Methods

Default methods solve a major problem: how do you add new methods to an interface without breaking all existing implementations?

public interface Collection<E> {
    // Original methods
    boolean add(E element);
    int size();
    
    // Added in Java 8 - doesn't break existing implementations
    default boolean isEmpty() {
        return size() == 0;
    }
    
    // Implementations can override if they have a more efficient way
}

Resolving Default Method Conflicts

When a class implements multiple interfaces with the same default method:

interface A {
    default void hello() { System.out.println("Hello from A"); }
}

interface B {
    default void hello() { System.out.println("Hello from B"); }
}

class C implements A, B {
    // Must override to resolve conflict
    @Override
    public void hello() {
        A.super.hello();  // Choose one, or provide new implementation
    }
}

Abstract classes and interfaces both enable polymorphism but differently. Abstract classes share code among related classes—they can have state and constructors. Interfaces define capabilities that unrelated classes can implement—multiple inheritance of type. Use abstract classes for "IS-A" relationships with shared implementation. Use interfaces for "CAN-DO" capabilities. A class can extend one abstract class but implement many interfaces.

© 2026 forEach. All rights reserved.

Privacy Policy•Terms of Service