Chapter 17 Intermediate 45 min min read Updated 2026-04-08

Abstraction - Abstract Classes and Interfaces

Practice Questions →

In This Chapter

What Is It?

What Is Abstraction in Java?

Abstraction is the process of hiding implementation details and exposing only the essential features. When Arjun uses System.out.println(), he does not know (or need to know) how Java internally writes bytes to the console. He just knows the method prints text. That is abstraction.

Java provides two mechanisms for abstraction:

  1. Abstract classes (partial abstraction): Can have both abstract (unimplemented) and concrete (implemented) methods.
  2. Interfaces (full abstraction, traditionally): Define a contract that implementing classes must follow.
// Abstract class
abstract class Shape {
    abstract double area();           // Abstract: no body
    void describe() {                 // Concrete: has body
        System.out.println("I am a shape with area: " + area());
    }
}

// Interface
interface Drawable {
    void draw();                      // Abstract by default
    default void erase() {            // Default method (Java 8)
        System.out.println("Erasing...");
    }
}

An abstract class says: "I have some common behavior, but subclasses must fill in the blanks." An interface says: "Any class that implements me guarantees it can do these things."

Why Does It Matter?

Why Does Abstraction Matter?

Abstraction is one of the four pillars of OOP. It directly influences how software is designed, maintained, and extended.

1. Hiding Complexity

When Kavya calls list.sort(), she does not care whether it uses merge sort, quicksort, or Tim sort. The implementation is hidden behind an abstract interface. This lets her focus on what to do, not how it is done internally.

2. Defining Contracts

An interface is a contract. When a class implements Comparable, it guarantees it has a compareTo() method. Any code that works with Comparable objects can rely on this contract without knowing the specific class.

3. Supporting Multiple Inheritance of Type

Java does not support multiple inheritance of classes (to avoid the diamond problem), but a class can implement multiple interfaces. This gives Ravi the flexibility of multiple inheritance without the ambiguity:

class SmartPhone implements Camera, GPS, Phone {
    // Must implement all methods from all three interfaces
}

4. Loose Coupling

Code that depends on interfaces rather than concrete classes is loosely coupled. If Priya writes a method that accepts List (interface), it works with ArrayList, LinkedList, or any future implementation. Swapping the implementation requires no code changes.

5. Framework and Library Design

Every major Java framework uses abstraction extensively. Spring's dependency injection works through interfaces. JDBC defines interfaces (Connection, Statement, ResultSet) that database vendors implement. Without abstraction, frameworks would be impossible.

6. Interview Relevance

"When would you use an abstract class vs an interface?" is one of the most asked Java interview questions. Understanding the distinction, especially with Java 8+ changes, is critical for placements.

Detailed Explanation

Detailed Explanation

1. Abstract Classes

An abstract class is declared with the abstract keyword. It can have abstract methods (no body) and concrete methods (with body). It cannot be instantiated directly.

abstract class Animal {
    String name;
    
    // Constructor (yes, abstract classes can have constructors)
    Animal(String name) {
        this.name = name;
    }
    
    // Abstract method: subclasses MUST implement
    abstract void sound();
    
    // Concrete method: inherited by subclasses
    void sleep() {
        System.out.println(name + " is sleeping");
    }
}

class Dog extends Animal {
    Dog(String name) {
        super(name);
    }
    
    @Override
    void sound() {
        System.out.println(name + " says: Bark!");
    }
}

// Animal a = new Animal("Generic");  // ERROR: Cannot instantiate abstract class
Animal a = new Dog("Rex");
a.sound();  // Rex says: Bark!
a.sleep();  // Rex is sleeping

Key points about abstract classes:

  • Declared with abstract keyword
  • Can have constructors (called by subclass constructors via super())
  • Can have instance variables (fields)
  • Can have both abstract and concrete methods
  • Cannot be instantiated with new
  • A subclass must implement all abstract methods, or declare itself abstract
  • Can have static methods, final methods, and any access modifier

2. Interfaces

An interface is a fully abstract type (traditionally). It defines a contract of methods that implementing classes must provide.

interface Flyable {
    void fly();                        // public abstract by default
    int MAX_ALTITUDE = 10000;          // public static final by default
}

interface Swimmable {
    void swim();
}

// A class can implement multiple interfaces
class Duck implements Flyable, Swimmable {
    @Override
    public void fly() {
        System.out.println("Duck flying");
    }
    
    @Override
    public void swim() {
        System.out.println("Duck swimming");
    }
}

Flyable f = new Duck();
f.fly();  // Duck flying

Swimmable s = new Duck();
s.swim(); // Duck swimming

Key points about interfaces:

  • All methods are public abstract by default (before Java 8)
  • All variables are public static final (constants)
  • A class uses implements (not extends) to implement an interface
  • A class can implement multiple interfaces (multiple inheritance of type)
  • An interface can extend another interface using extends
  • Cannot have constructors
  • Cannot have instance variables (only constants)

3. Abstract Class vs Interface

FeatureAbstract ClassInterface
Keywordabstract classinterface
MethodsAbstract + concreteAbstract (+ default/static since Java 8)
VariablesInstance variables (any type)Only public static final constants
ConstructorsYesNo
Multiple inheritanceNo (single extends)Yes (multiple implements)
Access modifiersAnypublic only (for methods)
When to useShared state + partial implementationDefine a contract / capability

4. When to Use Which

Use an abstract class when:

  • Subclasses share common state (fields) and behavior
  • You want to provide a partial implementation with some methods already coded
  • You need constructors to initialize shared state
  • Example: abstract class Vehicle with fields speed, fuel, and concrete methods like refuel()

Use an interface when:

  • You want to define a capability that unrelated classes can share
  • You need multiple inheritance of type
  • You want to enforce a contract without dictating implementation
  • Example: interface Serializable, interface Comparable

5. Default Methods in Interfaces (Java 8)

Java 8 introduced default methods in interfaces. These have a body and are inherited by implementing classes:

interface Logger {
    void log(String message);  // Abstract
    
    default void logInfo(String message) {  // Default method
        log("INFO: " + message);
    }
    
    default void logError(String message) {
        log("ERROR: " + message);
    }
}

class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println(message);
    }
    // logInfo and logError are inherited
}

ConsoleLogger cl = new ConsoleLogger();
cl.logInfo("Application started");   // INFO: Application started
cl.logError("File not found");       // ERROR: File not found

Default methods were added to allow interface evolution: adding new methods to existing interfaces without breaking all implementing classes.

6. Static Methods in Interfaces (Java 8)

Interfaces can also have static methods. These belong to the interface itself and are not inherited:

interface MathUtils {
    static int square(int n) {
        return n * n;
    }
    
    static int cube(int n) {
        return n * n * n;
    }
}

// Called on the interface, not on implementing classes
System.out.println(MathUtils.square(5));  // 25
System.out.println(MathUtils.cube(3));    // 27

7. Functional Interfaces

A functional interface has exactly one abstract method. It can be annotated with @FunctionalInterface (optional but recommended). Functional interfaces are the foundation of lambda expressions.

@FunctionalInterface
interface Converter {
    double convert(double value);  // Single abstract method
    
    // default and static methods are allowed
    default void describe() {
        System.out.println("I am a converter");
    }
}

// Lambda expression (because Converter is a functional interface)
Converter kmToMiles = (km) -> km * 0.621371;
Converter celToFar = (c) -> c * 9.0 / 5 + 32;

System.out.println(kmToMiles.convert(100));  // 62.1371
System.out.println(celToFar.convert(37));    // 98.6

Common built-in functional interfaces: Runnable (run()), Callable (call()), Comparator (compare()), Predicate (test()), Function (apply()).

8. Marker Interfaces

A marker interface has no methods. It simply "marks" a class as having a certain property. The JVM or frameworks check for this marker using instanceof.

// Built-in marker interfaces:
// java.io.Serializable — marks a class as serializable
// java.lang.Cloneable — marks a class as cloneable

import java.io.Serializable;

class Student implements Serializable {
    String name;
    int age;
    
    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

// Now Student objects can be serialized to a file or stream

With annotations available since Java 5, marker interfaces are less common for new APIs, but the existing ones (Serializable, Cloneable) remain important.

9. Interface Extending Interface

An interface can extend one or more interfaces:

interface Readable {
    void read();
}

interface Writable {
    void write();
}

interface ReadWritable extends Readable, Writable {
    void seek(int position);
}

// A class implementing ReadWritable must implement read(), write(), and seek()
class File implements ReadWritable {
    @Override
    public void read() { System.out.println("Reading file"); }
    @Override
    public void write() { System.out.println("Writing file"); }
    @Override
    public void seek(int pos) { System.out.println("Seeking to " + pos); }
}

Code Examples

Abstract Class with Constructors and Mixed Methods
abstract class Vehicle {
    String brand;
    int year;

    Vehicle(String brand, int year) {
        this.brand = brand;
        this.year = year;
    }

    abstract void start();
    abstract double fuelEfficiency();

    void displayInfo() {
        System.out.println(brand + " (" + year + ")");
        System.out.println("Fuel efficiency: " + fuelEfficiency() + " km/l");
    }
}

class Car extends Vehicle {
    Car(String brand, int year) {
        super(brand, year);
    }

    @Override
    void start() { System.out.println(brand + " car engine started"); }

    @Override
    double fuelEfficiency() { return 15.5; }
}

class Bike extends Vehicle {
    Bike(String brand, int year) {
        super(brand, year);
    }

    @Override
    void start() { System.out.println(brand + " bike kick-started"); }

    @Override
    double fuelEfficiency() { return 45.0; }
}

public class Main {
    public static void main(String[] args) {
        Vehicle v1 = new Car("Toyota", 2024);
        Vehicle v2 = new Bike("Honda", 2023);

        v1.start();
        v1.displayInfo();
        System.out.println();
        v2.start();
        v2.displayInfo();
    }
}
The abstract class Vehicle has a constructor, two abstract methods, and one concrete method. Car and Bike extend it, implementing the abstract methods. The concrete method displayInfo() calls the abstract fuelEfficiency(), which is resolved by dynamic dispatch.
Toyota car engine started Toyota (2024) Fuel efficiency: 15.5 km/l Honda bike kick-started Honda (2023) Fuel efficiency: 45.0 km/l
Interface Implementation and Multiple Interfaces
interface Printable {
    void print();
}

interface Scannable {
    void scan();
}

interface Faxable {
    void fax();
}

class AllInOnePrinter implements Printable, Scannable, Faxable {
    @Override
    public void print() { System.out.println("Printing document..."); }

    @Override
    public void scan() { System.out.println("Scanning document..."); }

    @Override
    public void fax() { System.out.println("Faxing document..."); }
}

class SimplePrinter implements Printable {
    @Override
    public void print() { System.out.println("Simple printing..."); }
}

public class Main {
    public static void main(String[] args) {
        AllInOnePrinter aio = new AllInOnePrinter();
        aio.print();
        aio.scan();
        aio.fax();

        System.out.println();

        // Polymorphism with interfaces
        Printable p = new AllInOnePrinter();
        p.print();
        // p.scan();  // Compilation error: Printable has no scan()

        System.out.println();

        Printable[] printers = { new AllInOnePrinter(), new SimplePrinter() };
        for (Printable pr : printers) {
            pr.print();
        }
    }
}
AllInOnePrinter implements three interfaces, demonstrating multiple inheritance of type. A Printable reference can only call print(), even if the actual object supports more. Arrays of interface types enable polymorphic processing.
Printing document... Scanning document... Faxing document... Printing document... Printing document... Simple printing...
Default and Static Methods in Interfaces (Java 8)
interface Greeting {
    void greet(String name);  // Abstract

    default void greetAll(String... names) {
        for (String name : names) {
            greet(name);
        }
    }

    static String formatName(String name) {
        return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
    }
}

class FormalGreeting implements Greeting {
    @Override
    public void greet(String name) {
        System.out.println("Good morning, Mr./Ms. " + name);
    }
}

class CasualGreeting implements Greeting {
    @Override
    public void greet(String name) {
        System.out.println("Hey, " + name + "!");
    }
}

public class Main {
    public static void main(String[] args) {
        FormalGreeting fg = new FormalGreeting();
        fg.greet("Arjun");
        fg.greetAll("Kavya", "Ravi", "Priya");

        System.out.println();

        CasualGreeting cg = new CasualGreeting();
        cg.greetAll("Arjun", "Kavya");

        System.out.println();
        System.out.println(Greeting.formatName("aRJUN"));
    }
}
The default method greetAll is inherited by both implementations and calls the abstract greet method polymorphically. The static method formatName is called on the interface itself, not on instances.
Good morning, Mr./Ms. Arjun Good morning, Mr./Ms. Kavya Good morning, Mr./Ms. Ravi Good morning, Mr./Ms. Priya Hey, Arjun! Hey, Kavya! Arjun
Functional Interface with Lambda Expression
@FunctionalInterface
interface MathOperation {
    double execute(double a, double b);
}

public class Main {
    static void compute(double a, double b, MathOperation op, String label) {
        System.out.println(label + ": " + op.execute(a, b));
    }

    public static void main(String[] args) {
        MathOperation add = (a, b) -> a + b;
        MathOperation subtract = (a, b) -> a - b;
        MathOperation multiply = (a, b) -> a * b;
        MathOperation divide = (a, b) -> b != 0 ? a / b : 0;

        compute(10, 5, add, "Add");
        compute(10, 5, subtract, "Subtract");
        compute(10, 5, multiply, "Multiply");
        compute(10, 5, divide, "Divide");
    }
}
A @FunctionalInterface with one abstract method can be implemented using lambda expressions. Each lambda provides a different implementation of execute(). The compute method uses polymorphism to call the appropriate operation.
Add: 15.0 Subtract: 5.0 Multiply: 50.0 Divide: 2.0
Abstract Class vs Interface: Combined Example
abstract class Employee {
    String name;
    double baseSalary;

    Employee(String name, double baseSalary) {
        this.name = name;
        this.baseSalary = baseSalary;
    }

    abstract double calculateSalary();

    void display() {
        System.out.println(name + " earns " + calculateSalary());
    }
}

interface Taxable {
    double calculateTax();

    default void showTax() {
        System.out.println("Tax: " + calculateTax());
    }
}

interface Bonusable {
    double getBonus();
}

class Manager extends Employee implements Taxable, Bonusable {
    Manager(String name, double baseSalary) {
        super(name, baseSalary);
    }

    @Override
    double calculateSalary() { return baseSalary + getBonus(); }

    @Override
    public double calculateTax() { return calculateSalary() * 0.3; }

    @Override
    public double getBonus() { return baseSalary * 0.2; }
}

public class Main {
    public static void main(String[] args) {
        Manager m = new Manager("Arjun", 80000);
        m.display();
        m.showTax();
        System.out.println("Bonus: " + m.getBonus());
    }
}
This example combines abstract classes and interfaces. Employee is an abstract class providing shared state and a template. Taxable and Bonusable are interfaces adding capabilities. Manager extends one and implements two, demonstrating both mechanisms working together.
Arjun earns 96000.0 Tax: 28800.0 Bonus: 16000.0
Interface Extending Interface and Marker Interface
import java.io.Serializable;

interface Identifiable {
    String getId();
}

interface Displayable {
    void display();
}

interface Entity extends Identifiable, Displayable {
    String getType();
}

class Student implements Entity, Serializable {
    private String id;
    private String name;

    Student(String id, String name) {
        this.id = id;
        this.name = name;
    }

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

    @Override
    public String getType() { return "Student"; }

    @Override
    public void display() {
        System.out.println(getType() + " [" + getId() + "]: " + name);
    }
}

public class Main {
    public static void main(String[] args) {
        Student s = new Student("S101", "Kavya");
        s.display();

        System.out.println("Is Identifiable? " + (s instanceof Identifiable));
        System.out.println("Is Serializable? " + (s instanceof Serializable));
        System.out.println("Is Entity? " + (s instanceof Entity));

        // Using interface reference
        Entity e = s;
        e.display();
    }
}
The Entity interface extends both Identifiable and Displayable. Student implements Entity (inheriting all three abstract methods) and the marker interface Serializable. The instanceof operator confirms all interface relationships.
Student [S101]: Kavya Is Identifiable? true Is Serializable? true Is Entity? true Student [S101]: Kavya

Common Mistakes

Trying to Instantiate an Abstract Class

abstract class Animal {
    abstract void sound();
}

Animal a = new Animal();  // Compilation error!
Compilation error: Animal is abstract; cannot be instantiated.
abstract class Animal {
    abstract void sound();
}

class Dog extends Animal {
    @Override
    void sound() { System.out.println("Bark"); }
}

Animal a = new Dog();  // Correct: instantiate a concrete subclass
a.sound();
Abstract classes cannot be instantiated directly because they may have unimplemented methods. You must create a concrete subclass that implements all abstract methods, then instantiate the subclass.

Forgetting to Make Interface Methods Public

interface Printable {
    void print();
}

class Report implements Printable {
    @Override
    void print() {  // Missing public!
        System.out.println("Printing report");
    }
}
Compilation error: attempting to assign weaker access privileges; was public.
interface Printable {
    void print();
}

class Report implements Printable {
    @Override
    public void print() {  // Must be public
        System.out.println("Printing report");
    }
}
All interface methods are implicitly public. When implementing, the method must also be public. Omitting the modifier makes it package-private (default), which is more restrictive than public, causing a compilation error.

Not Implementing All Abstract Methods

abstract class Shape {
    abstract double area();
    abstract double perimeter();
}

class Circle extends Shape {
    double radius;
    Circle(double r) { this.radius = r; }

    @Override
    double area() { return Math.PI * radius * radius; }
    // Forgot to implement perimeter()!
}
Compilation error: Circle is not abstract and does not override abstract method perimeter() in Shape.
class Circle extends Shape {
    double radius;
    Circle(double r) { this.radius = r; }

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

    @Override
    double perimeter() { return 2 * Math.PI * radius; }
}
A concrete (non-abstract) subclass must implement ALL abstract methods from its parent. If you cannot implement all of them, declare the subclass as abstract as well.

Adding Instance Variables to an Interface

interface User {
    String name;  // Compilation error in practice!
    int age = 0;  // This compiles but is public static final

    void login();
}
Variables in interfaces are implicitly public static final. 'String name;' without initialization causes a compilation error because final variables must be initialized.
interface User {
    int MAX_AGE = 150;  // public static final (constant)
    String DEFAULT_ROLE = "Guest";

    void login();
}

// For instance variables, use an abstract class
abstract class UserBase {
    String name;
    int age;

    abstract void login();
}
Interfaces cannot have instance variables. All variables in interfaces are implicitly public static final (constants) and must be initialized. For instance state, use an abstract class.

Using @FunctionalInterface on an Interface with Multiple Abstract Methods

@FunctionalInterface
interface Calculator {
    int add(int a, int b);
    int subtract(int a, int b);  // Second abstract method!
}
Compilation error: Calculator is not a functional interface. Multiple non-overriding abstract methods found.
@FunctionalInterface
interface Calculator {
    int compute(int a, int b);  // Single abstract method

    default int addDefault(int a, int b) { return a + b; }  // Default is fine
    static int multiply(int a, int b) { return a * b; }     // Static is fine
}
A functional interface must have exactly ONE abstract method. Default methods and static methods do not count as abstract, so they are allowed. The @FunctionalInterface annotation enforces this rule at compile time.

Summary

  • Abstraction hides implementation details and exposes only essential features. Java achieves it through abstract classes and interfaces.
  • An abstract class (abstract keyword) can have abstract methods (no body), concrete methods (with body), constructors, and instance variables. It cannot be instantiated directly.
  • An interface defines a contract of methods. All methods are public abstract by default. All variables are public static final constants.
  • A class extends one abstract class but can implement multiple interfaces. This gives Java multiple inheritance of type without the diamond problem.
  • Use abstract classes when subclasses share common state and partial behavior. Use interfaces when defining a capability or contract for unrelated classes.
  • Default methods (Java 8) allow interfaces to have methods with a body. This enables adding new methods to existing interfaces without breaking implementations.
  • Static methods in interfaces (Java 8) are called on the interface itself, not on instances. They are not inherited by implementing classes.
  • A functional interface has exactly one abstract method and can be used with lambda expressions. The @FunctionalInterface annotation enforces this at compile time.
  • Common functional interfaces include Runnable, Callable, Comparator, Predicate, and Function.
  • Marker interfaces (Serializable, Cloneable) have no methods. They tag a class with a capability that the JVM or frameworks check with instanceof.
  • An interface can extend multiple interfaces. A class implementing such an interface must implement all inherited abstract methods.

Ready to Practice?

Test your understanding with 50+ practice questions on this topic.

Go to Practice Questions

Want to learn Java with a live mentor?

Explore our Java Masterclass