Index

Abstract Classes and Interfaces

Java for Beginners

8.1 Abstract Methods and Classes

In Java, abstract classes and abstract methods offer a powerful way to define common behavior while leaving some parts incomplete—forcing subclasses to fill in the details. This approach provides a flexible blueprint for creating related classes with shared features, without allowing direct instantiation of the abstract class itself.

What is an Abstract Class?

An abstract class is a class that cannot be instantiated directly. It can contain both concrete methods (with full implementations) and abstract methods (without implementations). Abstract classes are declared with the keyword abstract.

public abstract class Vehicle {
    // Abstract method (no implementation)
    public abstract void move();

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

Because Vehicle is abstract, you cannot create objects of type Vehicle:

Vehicle v = new Vehicle();  // Compilation error!

What is an Abstract Method?

An abstract method declares a method signature but no body. It tells subclasses, "You must provide your own implementation of this method."

Example:

public abstract void move();

Every concrete subclass must override and provide an implementation for all abstract methods inherited from its abstract superclass unless the subclass is also abstract.

When to Use Abstract Classes?

Use abstract classes when:

If all methods are abstract and no state (fields) is needed, consider using an interface instead (covered later).

Example: Abstract Vehicle Class and Subclasses

// Abstract superclass
public abstract class Vehicle {
    public abstract void move();  // Abstract method

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

// Concrete subclass Car
public class Car extends Vehicle {
    @Override
    public void move() {
        System.out.println("Car is driving on the road");
    }
}

// Concrete subclass Bike
public class Bike extends Vehicle {
    @Override
    public void move() {
        System.out.println("Bike is pedaling on the path");
    }
}

Using the Classes

public class Main {
    public static void main(String[] args) {
        Vehicle myCar = new Car();
        Vehicle myBike = new Bike();

        myCar.start();
        myCar.move();

        myBike.start();
        myBike.move();
    }
}

Output:

Vehicle is starting
Car is driving on the road
Vehicle is starting
Bike is pedaling on the path

Notice that even though myCar and myBike are both declared as Vehicle references, the correct subclass implementations of move() are called. This is polymorphism working hand-in-hand with abstraction.

Click to view full runnable Code

abstract class Vehicle {
    public abstract void move();  // Abstract method

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

class Car extends Vehicle {
    @Override
    public void move() {
        System.out.println("Car is driving on the road");
    }
}

class Bike extends Vehicle {
    @Override
    public void move() {
        System.out.println("Bike is pedaling on the path");
    }
}

public class Main {
    public static void main(String[] args) {
        Vehicle myCar = new Car();
        Vehicle myBike = new Bike();

        myCar.start();
        myCar.move();

        myBike.start();
        myBike.move();
    }
}

Benefits of Abstract Classes

Summary

Try creating your own abstract class and subclasses to practice how abstract methods enforce behavior while allowing flexible implementations.

Index

8.2 Implementing Interfaces

In Java, interfaces define a contract that classes agree to follow by implementing specific methods. Unlike classes, interfaces only specify what should be done, not how to do it. This helps create flexible, loosely coupled code where multiple classes can share common behaviors without inheriting from the same superclass.

What is an Interface?

An interface is like a blueprint for classes. It declares method signatures that implementing classes must provide. This ensures consistency and allows code to rely on these common methods without needing to know the exact class details.

Defining an Interface

The syntax for defining an interface is:

public interface Flyable {
    void fly();  // abstract method
}

All methods in an interface are implicitly public and abstract (before Java 8). Classes that implement this interface must provide a concrete fly() method.

Implementing an Interface

To implement an interface, a class uses the implements keyword:

public class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("Bird is flying");
    }
}

The class must implement all methods declared in the interface unless the class is abstract.

Multiple Interface Implementation

Java allows a class to implement multiple interfaces, enabling the class to have diverse capabilities:

public interface Swimmable {
    void swim();
}

public class Fish implements Swimmable {
    @Override
    public void swim() {
        System.out.println("Fish is swimming");
    }
}

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");
    }
}

Default Methods (Java 8)

Since Java 8, interfaces can include default methods — methods with a body that implementing classes inherit automatically:

public interface Flyable {
    void fly();

    default void takeOff() {
        System.out.println("Taking off");
    }
}

public class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("Bird is flying");
    }
}

The Bird class can call takeOff() even if it doesn't override it, providing flexibility and backward compatibility when evolving interfaces.

Example: Using Flyable and Swimmable

public class Main {
    public static void main(String[] args) {
        Flyable bird = new Bird();
        Swimmable fish = new Fish();
        Duck duck = new Duck();

        bird.fly();          // Bird is flying
        fish.swim();         // Fish is swimming
        duck.fly();          // Duck is flying
        duck.swim();         // Duck is swimming
        duck.takeOff();      // Taking off (from Flyable default method)
    }
}

Why Use Interfaces?

Click to view full runnable Code

interface Swimmable {
    void swim();
}

interface Flyable {
    void fly();

    default void takeOff() {
        System.out.println("Taking off");
    }
}

class Fish implements Swimmable {
    @Override
    public void swim() {
        System.out.println("Fish is swimming");
    }
}

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");
    }
}

class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("Bird is flying");
    }
}

public class Main {
    public static void main(String[] args) {
        Flyable bird = new Bird();
        Swimmable fish = new Fish();
        Duck duck = new Duck();

        bird.fly();      // Bird is flying
        fish.swim();     // Fish is swimming
        duck.fly();      // Duck is flying
        duck.swim();     // Duck is swimming
        duck.takeOff();  // Taking off (from default method)
    }
}

Summary

Try defining your own interfaces and implementing them in different classes to see how interfaces empower polymorphism and clean architecture in Java.

Index

8.3 Interface vs Abstract Class

In Java, both interfaces and abstract classes help define abstract types that other classes can build upon. However, they serve different purposes and have important differences. Understanding when to use each is key to designing clean, maintainable code.

Key Differences Between Interface and Abstract Class

Feature Interface Abstract Class
Multiple Inheritance Supports multiple inheritance (a class can implement many interfaces) Does not support multiple inheritance (a class can extend only one class)
Method Implementation Before Java 8, methods were abstract only; since Java 8, interfaces can have default and static methods with implementations Can have both abstract and fully implemented methods
Fields/State Can only have public static final constants (no instance variables) Can have instance variables (state)
Constructors No constructors (cannot instantiate) Can have constructors to initialize state
Purpose Specifies capabilities or contracts that classes agree to implement Provides a base partial implementation with shared code
Usage Scenario Use when unrelated classes share common behavior, or to define capabilities like Comparable, Runnable Use when classes share code, fields, or default behavior and belong to the same family
Inheritance Requirement Classes must implement interface methods or be declared abstract Subclasses inherit abstract methods to implement and concrete methods to reuse

When to Use an Interface?

Example:

public interface Flyable {
    void fly();
    default void takeOff() {
        System.out.println("Taking off");
    }
}

When to Use an Abstract Class?

Example:

public abstract class Vehicle {
    protected int speed;

    public Vehicle(int speed) {
        this.speed = speed;
    }

    public abstract void move();

    public void stop() {
        System.out.println("Vehicle stopped");
    }
}

Compatibility and Recent Enhancements

Example:

abstract class Vehicle {
    protected int speed;

    public Vehicle(int speed) {
        this.speed = speed;
    }

    public abstract void move();

    public void stop() {
        System.out.println("Vehicle stopped");
    }
}

class Car extends Vehicle {
    public Car(int speed) {
        super(speed);
    }

    @Override
    public void move() {
        System.out.println("Car is moving at " + speed + " km/h");
    }
}

class Bike extends Vehicle {
    public Bike(int speed) {
        super(speed);
    }

    @Override
    public void move() {
        System.out.println("Bike is cruising at " + speed + " km/h");
    }
}

public class Main {
    public static void main(String[] args) {
        Vehicle myCar = new Car(100);
        Vehicle myBike = new Bike(25);

        myCar.move();
        myCar.stop();

        myBike.move();
        myBike.stop();
    }
}

Summary

Experiment with both interfaces and abstract classes in your projects. Seeing their differences firsthand will help you decide which tool fits your design goals.

Index

8.4 Functional Interfaces and Lambda Basics

Java 8 introduced a powerful new feature called lambda expressions, which bring functional programming concepts into Java. At the heart of this feature are functional interfaces—interfaces with exactly one abstract method—that enable writing concise, expressive code.

What is a Functional Interface?

A functional interface is an interface with a single abstract method (SAM). It represents a single behavior or action, making it a perfect target for lambda expressions.

Java provides the @FunctionalInterface annotation to explicitly declare an interface as functional, which helps the compiler catch mistakes:

@FunctionalInterface
public interface Greeting {
    void sayHello(String name);
}

Lambda Expression Syntax

A lambda expression provides a clear, concise way to implement the single abstract method of a functional interface without writing an entire class or anonymous class.

General syntax:

(parameters) -> expression

or

(parameters) -> { statements; }

Example: Using Lambda with Runnable

Before Java 8, implementing Runnable required an anonymous inner class:

Runnable task = new Runnable() {
    @Override
    public void run() {
        System.out.println("Task running...");
    }
};
new Thread(task).start();

With lambdas:

Runnable task = () -> System.out.println("Task running...");
new Thread(task).start();

This is shorter and easier to read.

Example: Using Lambda with Comparator

Comparator<T> is a functional interface used to compare objects:

Anonymous class way:

Comparator<String> comp = new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        return s1.length() - s2.length();
    }
};

With lambda:

Comparator<String> comp = (s1, s2) -> s1.length() - s2.length();

Benefits of Lambdas and Functional Interfaces

Integration with Java Stream API

Functional interfaces are key to the Java Stream API, which enables powerful data processing pipelines.

Example: Filtering and printing a list of names that start with "J":

import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("John", "Alice", "Jack", "Bob");

        names.stream()
             .filter(name -> name.startsWith("J"))  // Lambda as Predicate functional interface
             .forEach(name -> System.out.println(name));  // Lambda as Consumer functional interface
    }
}

Output:

John
Jack

Common Functional Interfaces in java.util.function

Java provides many built-in functional interfaces:

Interface Abstract Method Description
Runnable void run() Represents a task to run
Comparator<T> int compare(T o1, T o2) Compares two objects
Predicate<T> boolean test(T t) Tests a condition on input
Consumer<T> void accept(T t) Performs an action on input
Function<T,R> R apply(T t) Transforms input to output
Supplier<T> T get() Supplies a value without input

These interfaces are designed to be used with lambdas and method references.

Summary

Give it a try: rewrite your anonymous classes using lambdas and explore how functional interfaces simplify your code!

Index