Index

Interfaces and Abstract Types

Java Syntax

9.1 Declaring and Implementing Interfaces

In Java, interfaces provide a powerful way to define contracts that classes can implement. An interface declares method signatures (and, since Java 8, default and static methods) without providing full implementations, allowing different classes to share a common set of behaviors without forcing a specific class hierarchy.

Defining an Interface

To declare an interface, use the interface keyword followed by the interface name and a body containing method signatures:

public interface Vehicle {
    void start();
    void stop();
}

In this example, Vehicle is an interface with two methods: start() and stop(). These methods are implicitly public and abstract, so you don't specify these modifiers explicitly.

Implementing an Interface

A class implements an interface by using the implements keyword and providing concrete implementations for all of the interface's methods:

public class Car implements Vehicle {
    @Override
    public void start() {
        System.out.println("Car is starting.");
    }

    @Override
    public void stop() {
        System.out.println("Car is stopping.");
    }
}

Here, Car commits to the Vehicle contract by implementing start() and stop(). The @Override annotation helps catch errors if method signatures don't match.

Implementing Multiple Interfaces

Java supports multiple interface inheritance, meaning a class can implement more than one interface:

public interface Electric {
    void charge();
}

public class ElectricCar implements Vehicle, Electric {
    @Override
    public void start() {
        System.out.println("ElectricCar is starting silently.");
    }

    @Override
    public void stop() {
        System.out.println("ElectricCar is stopping.");
    }

    @Override
    public void charge() {
        System.out.println("ElectricCar is charging.");
    }
}

ElectricCar implements both Vehicle and Electric, so it must implement all methods from both interfaces. This ability lets you mix behaviors flexibly without tying your classes to a rigid class inheritance structure.

Interface Inheritance vs. Class Inheritance

Why Are Interfaces Useful for Abstraction?

  1. Decoupling: Interfaces separate what a class should do from how it does it. This encourages coding to an interface rather than to an implementation, promoting loose coupling and easier maintenance.

  2. Multiple Behavior Inheritance: Since Java does not support multiple inheritance of classes, interfaces allow a class to have multiple capabilities by implementing multiple interfaces.

  3. Polymorphism: You can write code that works on the interface type, allowing different implementations to be plugged in without changing client code.

public void testVehicle(Vehicle v) {
    v.start();
    v.stop();
}

This method can accept any object that implements Vehicle, whether it's a Car, ElectricCar, or another class entirely.

  1. API Design: Interfaces define clear, minimal contracts for APIs and libraries, improving readability and enforceability.

Summary Example

public interface Flyable {
    void fly();
}

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

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

Both Bird and Airplane share the Flyable interface, but their implementations of fly() differ, illustrating polymorphism with interfaces.

Click to view full runnable Code

class Animal {
    void speak() {
        System.out.println("The animal makes a sound.");
    }
}

class Dog extends Animal {
    @Override
    void speak() {
        System.out.println("The dog barks.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myPet = new Dog();  // Upcasting
        myPet.speak();             // Outputs: The dog barks.
    }
}

Reflection

Interfaces provide a way to enforce consistent behavior across unrelated classes, enabling flexibility and extensibility in Java programs. Unlike class inheritance, which shares implementation and can lead to complex hierarchies, interfaces define a clear contract without imposing a fixed inheritance structure. This abstraction fosters modular, maintainable, and testable code — essential qualities in modern software development.

By mastering interfaces and their implementation, you unlock a core aspect of Java's design philosophy, empowering you to write clean, scalable, and robust applications.

Index

9.2 Functional Interfaces and @FunctionalInterface

In Java, a functional interface is a special kind of interface that has exactly one abstract method. This single abstract method defines the contract of the interface and enables instances of the interface to be created with lambda expressions, method references, or anonymous classes — a core foundation of functional programming in Java.

Defining a Functional Interface

A functional interface contains only one abstract method, but it can have any number of default or static methods. Here's an example of a functional interface named Calculator:

@FunctionalInterface
public interface Calculator {
    int calculate(int a, int b);
}

Purpose of @FunctionalInterface

The @FunctionalInterface annotation serves several important roles:

  1. Compile-Time Enforcement: It ensures the interface has exactly one abstract method. If you accidentally add another abstract method, the compiler will produce an error. This prevents accidental violations of the functional interface contract.

  2. Documentation: It clearly signals to readers that the interface is intended for functional programming use, making the code easier to understand and maintain.

  3. Compatibility: Some APIs and frameworks may specifically require or optimize for functional interfaces marked with this annotation.

Without this annotation, an interface with one abstract method can still be used as a functional interface, but you lose the safety net provided by the compiler.

Using Functional Interfaces with Lambda Expressions

Functional interfaces are critical for using lambda expressions in Java. Lambdas provide a concise syntax to implement the single abstract method without writing verbose anonymous classes.

For example, using the Calculator interface:

Calculator add = (a, b) -> a + b;
Calculator multiply = (a, b) -> a * b;

System.out.println(add.calculate(5, 3));       // Output: 8
System.out.println(multiply.calculate(5, 3));  // Output: 15

Here, add and multiply are lambda expressions implementing the calculate method. The lambda (a, b) -> a + b means: given a and b, return their sum.

This style dramatically reduces boilerplate and improves code readability, especially when used with standard Java libraries that use functional interfaces, such as the java.util.function package.

Common Built-In Functional Interfaces

Java provides many ready-to-use functional interfaces in the java.util.function package, including:

Each of these has a single abstract method, making them functional interfaces suitable for lambdas.

Functional Interfaces and Functional Programming

The introduction of functional interfaces was a major step toward embracing functional programming paradigms in Java:

By defining and using functional interfaces, Java enables these patterns while maintaining backward compatibility with the object-oriented core of the language.

Example: Custom Functional Interface with Default Methods

Functional interfaces can also contain default methods without breaking their contract:

@FunctionalInterface
public interface Printer {
    void print(String message);

    default void printTwice(String message) {
        print(message);
        print(message);
    }
}

This interface has one abstract method, print, and a default method, printTwice. A lambda can implement print, and the default method can be used as-is:

Printer printer = msg -> System.out.println(msg);
printer.print("Hello");        // Prints "Hello"
printer.printTwice("Hello");   // Prints "Hello" twice
Click to view full runnable Code

@FunctionalInterface
interface Calculator {
    int calculate(int a, int b);
}

@FunctionalInterface
interface Printer {
    void print(String message);

    default void printTwice(String message) {
        print(message);
        print(message);
    }
}

public class Main {
    public static void main(String[] args) {
        // Using Calculator interface with lambdas
        Calculator add = (a, b) -> a + b;
        Calculator multiply = (a, b) -> a * b;

        System.out.println("Addition: " + add.calculate(5, 3));       // 8
        System.out.println("Multiplication: " + multiply.calculate(5, 3)); // 15

        // Using Printer interface with a lambda
        Printer printer = msg -> System.out.println(msg);
        printer.print("Hello");
        printer.printTwice("World");
    }
}

Reflection

Functional interfaces are a cornerstone of modern Java programming, bridging the gap between traditional object-oriented approaches and functional programming techniques. The @FunctionalInterface annotation provides clarity and safety, ensuring that your interfaces remain suitable for lambda expressions and method references.

By leveraging functional interfaces, Java developers can write more concise, expressive, and modular code. They unlock the power of functional programming patterns—such as higher-order functions and immutability—while retaining the strengths and familiarity of Java's object-oriented roots.

Understanding and using functional interfaces effectively will prepare you for working with streams, asynchronous programming, and modern APIs that rely heavily on these constructs.

Index

9.3 Default and Static Methods in Interfaces

Before Java 8, interfaces could only declare abstract methods — methods without a body — meaning any class implementing the interface had to provide its own implementation. This limitation made evolving interfaces difficult because adding a new method to an interface would break all existing implementations.

Default Methods

Java 8 introduced default methods to address this problem. A default method provides a method body inside the interface itself, allowing the interface to supply a default implementation. Classes that implement the interface can either use this default or override it.

Here's an example of an interface with a default method:

public interface Vehicle {
    void start();

    default void stop() {
        System.out.println("Stopping the vehicle.");
    }
}

In this example:

A class implementing Vehicle can choose to override stop() or use the provided default:

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

    // stop() method inherited by default
}

Static Methods

Java 8 also added static methods to interfaces. These are methods that belong to the interface itself rather than any instance, similar to static methods in classes.

Example:

public interface MathUtils {
    static int square(int x) {
        return x * x;
    }
}

Static methods in interfaces are called using the interface name:

int result = MathUtils.square(5);  // Returns 25
Click to view full runnable Code

interface Vehicle {
    void start();

    default void stop() {
        System.out.println("Stopping the vehicle.");
    }
}

class Car implements Vehicle {
    @Override
    public void start() {
        System.out.println("Car started.");
    }
    // Inherits stop()
}

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

public class Main {
    public static void main(String[] args) {
        Vehicle myCar = new Car();
        myCar.start();  // Output: Car started.
        myCar.stop();   // Output: Stopping the vehicle.

        int result = MathUtils.square(5);
        System.out.println("Square of 5: " + result);  // Output: Square of 5: 25
    }
}

Why Were Default and Static Methods Added?

The main reasons were:

Reflection

Default and static methods in interfaces represent a significant evolution in Java's type system. They enable interface evolution without sacrificing backward compatibility — a critical concern for large-scale, widely used APIs.

By allowing interfaces to hold shared logic, Java promotes cleaner design and easier maintenance. However, care should be taken to avoid overly complex default methods that can obscure the primary role of interfaces as contracts.

In summary, default and static methods enhance flexibility and expressiveness in Java interfaces, bridging the gap between pure abstraction and practical implementation.

Index

9.4 Abstract Classes and Methods

In Java, abstract classes provide a way to define classes that cannot be instantiated on their own but serve as a foundation for other classes. Abstract classes can contain both abstract methods (methods without implementation) and concrete methods (methods with implementation). This allows for partial implementation and code reuse across subclasses.

Declaring an Abstract Class and Abstract Method

To declare an abstract class, use the abstract keyword in the class declaration. Similarly, abstract methods inside the class are declared with abstract and do not have a body.

public abstract class Animal {
    // Abstract method: no body
    public abstract void makeSound();

    // Concrete method: has implementation
    public void eat() {
        System.out.println("This animal is eating.");
    }
}

Here, Animal is an abstract class with an abstract method makeSound() and a concrete method eat().

Concrete Subclass Implementing Abstract Method

A subclass that extends an abstract class must implement all abstract methods, or it too becomes abstract.

public class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

Now, Dog is a concrete class with an implementation of makeSound(). We can create objects of Dog but not of Animal.

Abstract Classes vs. Interfaces

Aspect Abstract Class Interface
Can contain Abstract methods, concrete methods, fields Only abstract methods (pre-Java 8), now can have default and static methods
Multiple inheritance No (Java doesn't support multiple inheritance) Yes (a class can implement multiple interfaces)
Use case Partial implementation, shared state (fields) Pure abstraction, defining contracts
Constructors Can have constructors Cannot have constructors

When to use which?

Click to view full runnable Code

// Abstract class with an abstract and a concrete method
abstract class Animal {
    public abstract void makeSound();  // Abstract method

    public void eat() {                // Concrete method
        System.out.println("This animal is eating.");
    }
}

// Concrete subclass that implements the abstract method
class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}

public class Main {
    public static void main(String[] args) {
        // Animal a = new Animal(); // ❌ Not allowed: cannot instantiate abstract class

        Dog d = new Dog();            // ✅ Concrete class
        d.makeSound();                // Woof!
        d.eat();                      // This animal is eating.
    }
}

Reflection

Abstract classes play a vital role in Java's type system by allowing partial implementations, promoting code reuse while still enforcing a contract for subclasses. Unlike interfaces, abstract classes can hold state and concrete methods, enabling shared functionality and reducing code duplication.

However, since Java supports only single inheritance of classes, abstract classes should be used when there is a clear "is-a" relationship and shared implementation is needed. Interfaces, enhanced since Java 8 with default methods, are better suited for defining capabilities that can cross-cut unrelated classes.

By balancing the use of abstract classes and interfaces, Java programmers can create flexible, maintainable designs that leverage both abstraction and reuse effectively.

Index