Java

Beyond the Basics: Abstract Classes and Interfaces Explained

Java includes two important building blocks for developing custom types: Abstract Classes and Interfaces. Both are essential for establishing abstraction and polymorphism, but they serve different purposes and have various advantages. In this piece, we will look at these principles, their applications, and best practices for their use.

Everything in Java Needs a Type

Java is a statically typed language, which means that each variable, parameter, return value, and object must be assigned a type at compile time. This enables rigorous type checking and minimizes runtime errors. Abstract classes and interfaces are two mechanism that help developers define new types in a systematic and flexible manner. They allow for the creation of reusable, extensible, and maintainable code while adhering to the principles of object-oriented programming.

Abstract classes allow you to define a type that encapsulate shared behavior and state across related classes. Interfaces, on the other hand, describe a type as a contract that specifies methods that every class that follows it must implement. These two components lay the groundwork for developing flexible APIs and implementing polymorphism in Java.

What is an Abstract Class?

An abstract class cannot be instantiated directly. It may include both abstract methods (no implementation) and concrete methods (with implementation). Abstract classes serve as the blueprint for subclasses.

Example of an Abstract Class:

public abstract class Vehicle {
    protected String brand;
    protected int year;

    // Concrete method with implementation
    public void start() {
        System.out.println("Vehicle starting...");
    }

    // Abstract method - must be implemented by subclasses
    public abstract double calculateFuelEfficiency();

    // Another abstract method
    public abstract void maintenance();
}

// Concrete implementation
public class Car extends Vehicle {
    @Override
    public double calculateFuelEfficiency() {
        return 25.5; // Miles per gallon
    }

    @Override
    public void maintenance() {
        System.out.println("Performing car maintenance");
    }
}
abstract class Animal {
    private String name;

    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    // Abstract method
    public abstract void makeSound();
}

class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(getName() + " says Woof!");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog("Buddy");
        dog.makeSound();
    }
}

Abstract Classes and Polymorphism:

Abstract classes are key in obtaining runtime polymorphism. As shown above, the variable dog is declared as Animal but references a Dog instance. This gives for more freedom in method overriding and object substitution.

Vehicle car = new Car();
Vehicle truck = new Truck();

// Polymorphic calls
car.maintenance();    // Calls Car's implementation
truck.maintenance();  // Calls Truck's implementation

What is an Interface?

Before Java 8, an interface was a completely abstract type. It could only have method declarations and constants. Methods declared in an interface that do not have an implementation are by default public and abstract. Interfaces defines a contract that implementing classes must fulfill.

Example of an Interface (Pre-Java 8):

public interface Payable {
    double calculatePay();
    void processPay();
}

public interface Taxable {
    double calculateTax();
}

More Examples:

interface Vehicle {
    void startEngine();
    void stopEngine();
}

class Car implements Vehicle {
    @Override
    public void startEngine() {
        System.out.println("Car engine started.");
    }

    @Override
    public void stopEngine() {
        System.out.println("Car engine stopped.");
    }
}

public class Main {
    public static void main(String[] args) {
        Vehicle car = new Car();
        car.startEngine();
        car.stopEngine();
    }
}

Application of Interfaces: Object Creation with Factory Pattern

Interfaces are important in creating the Factory Pattern, which abstracts object creation logic into factory methods.

Example of Static Factory Method:

interface Shape {
    void draw();
}

class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a Circle.");
    }
}

class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a Rectangle.");
    }
}

class Triangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a Triangle.");
    }
}

class ShapeFactory {
    public static Shape getShape(String type) {
        if (type.equalsIgnoreCase("circle")) {
            return new Circle();
        } else if (type.equalsIgnoreCase("rectangle")) {
            return new Rectangle();
        } else if (type.equalsIgnoreCase("triangle")) {
            return new Triangle();
        }
        throw new IllegalArgumentException("Unknown shape type");
    }
}

public class Main {
    public static void main(String[] args) {
        Shape circle = ShapeFactory.getShape("circle");
        circle.draw();

        Shape rectangle = ShapeFactory.getShape("rectangle");
        rectangle.draw();

        Shape triangle = ShapeFactory.getShape("triangle");
        triangle.draw();
    }
}

Advantages of Static Factory Methods:

  • Clear object creation logic.
  • Flexibility to return a cached instance or subclass.
  • Eliminates the problem of multiple constructors with similar signatures.

Interfaces as Types

Interfaces are extremely versatile. They can act as:

  • Variable types:
Vehicle vehicle = new Car();
  • Argument types:
public void operateVehicle(Vehicle vehicle) {
    vehicle.startEngine();
}
  • Return types:
public Vehicle getVehicle() {
    return new Car();
}

Interface vs Abstract Class

Interface vs Abstract Class: Key Differences

  • Multiple Inheritance: Classes can extend only one abstract class Classes can implement multiple interfaces
  • State: Abstract classes can have instance variables Interfaces can only have constants (public static final)
  • Method Implementation: Abstract classes can have concrete methods Interfaces (pre-Java 8) could only have abstract methods

Advantages of Interfaces

  • Decoupling: Interfaces provide complete separation between specification and implementation
  • Multiple Implementation: A class can implement multiple interfaces
  • Evolution: Easier to evolve with default methods (Java 8+)
  • Testing: Easier to mock in unit tests

Best Practices: When to Use Interfaces

Use interfaces when:

  • You need to define a contract without implementation
  • Multiple inheritance is required
  • You need to specify behavior that can be implemented by many unrelated classes

Use abstract classes when:

  • You have common functionality to share
  • You need to maintain state
  • You want to provide a base implementation with some abstract methods

Evolving API Problem

Prior to Java 8, introducing new methods to an interface required all implementing classes to update their code. This resulted in the "Evolving API Problem." The introduction of default methods allows for the addition of new functionality to interfaces without breaking existing implementations.

Example of Default Method:

interface Printer {
    void print();

    default void showStatus() {
        System.out.println("Printer is online.");
    }
}

class LaserPrinter implements Printer {
    @Override
    public void print() {
        System.out.println("Printing document...");
    }
}

public class Main {
    public static void main(String[] args) {
        Printer printer = new LaserPrinter();
        printer.print();
        printer.showStatus();
    }
}

About

At DevelopersMonk, we share tutorials, tips, and insights on modern programming frameworks like React, Next.js, Spring Boot, and more. Join us on our journey to simplify coding and empower developers worldwide!

Email: developersmonks@gmail.com

Phone: +23359*******

Quick Links

  • Home
  • About
  • Contact Us
  • Categories

  • Java
  • TypeScript
  • JavaScript
  • React
  • Nextjs
  • Spring Boot
  • DevOps