Java 8 – How to Implement Callable with Functional Interface

Introduction

Java 8 introduced lambda expressions and functional interfaces, making it easier to write concise and readable code for common tasks. The Callable interface is a functional interface that represents a task that can be executed by a thread and returns a result. Unlike Runnable, which does not return a result, Callable allows you to return a value or throw a checked exception. In this guide, we’ll explore how to implement the Callable interface using lambda expressions in Java 8.

Table of Contents

  • Problem Statement
  • Solution Steps
  • Java Program
    • Example 1: Implementing Callable Without Lambda Expressions
    • Example 2: Implementing Callable with Lambda Expressions
    • Example 3: Executing Multiple Callable Tasks Using ExecutorService
  • Conclusion

Problem Statement

The Callable interface is commonly used when you need to execute tasks that return a result or may throw a checked exception. Traditionally, implementing Callable required writing verbose anonymous classes. The goal is to implement Callable using lambda expressions to simplify the code and make it more readable.

Example:

  • Problem: Writing custom tasks that return a result using anonymous classes can be verbose and harder to maintain.
  • Goal: Use lambda expressions to implement Callable in a concise and readable way.

Solution Steps

  1. Implement Callable Using Anonymous Classes: Demonstrate the traditional way of implementing Callable without lambda expressions.
  2. Simplify with Lambda Expressions: Use lambda expressions to implement Callable more concisely.
  3. Execute Multiple Callable Tasks: Use ExecutorService to execute multiple Callable tasks and retrieve their results.

Java Program

Example 1: Implementing Callable Without Lambda Expressions

Before Java 8, implementing Callable typically involved creating an anonymous class. Here’s how it was done:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * Java 8 - Implement Callable Without Lambda Expressions
 * Author: https://www.rameshfadatare.com/
 */
public class CallableExample1 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // Implementing Callable using an anonymous class
        Callable<String> task = new Callable<String>() {
            @Override
            public String call() throws Exception {
                return "Task executed using anonymous class.";
            }
        };

        // Execute the Callable task using ExecutorService
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> result = executor.submit(task);

        // Get the result
        System.out.println(result.get());

        // Shutdown the executor service
        executor.shutdown();
    }
}

Output

Task executed using anonymous class.

Explanation

  • Anonymous Class: The Callable interface is implemented using an anonymous class, which requires a significant amount of boilerplate code.

Example 2: Implementing Callable with Lambda Expressions

Java 8 allows you to replace the anonymous class with a lambda expression, significantly simplifying the code.

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * Java 8 - Implement Callable with Lambda Expressions
 * Author: https://www.rameshfadatare.com/
 */
public class CallableExample2 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // Implementing Callable using a lambda expression
        Callable<String> task = () -> "Task executed using lambda expression.";

        // Execute the Callable task using ExecutorService
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> result = executor.submit(task);

        // Get the result
        System.out.println(result.get());

        // Shutdown the executor service
        executor.shutdown();
    }
}

Output

Task executed using lambda expression.

Explanation

  • Lambda Expression: The lambda expression () -> "Task executed using lambda expression." replaces the anonymous class, making the code more concise and easier to read.

Example 3: Executing Multiple Callable Tasks Using ExecutorService

You can use lambda expressions to create and execute multiple Callable tasks more efficiently.

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * Java 8 - Execute Multiple Callable Tasks with Lambda Expressions
 * Author: https://www.rameshfadatare.com/
 */
public class CallableExample3 {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // Create a list of Callable tasks using lambda expressions
        List<Callable<String>> tasks = Arrays.asList(
            () -> "Task 1 executed.",
            () -> "Task 2 executed.",
            () -> "Task 3 executed."
        );

        // Execute the Callable tasks using ExecutorService
        ExecutorService executor = Executors.newFixedThreadPool(3);
        List<Future<String>> results = executor.invokeAll(tasks);

        // Get the results
        for (Future<String> result : results) {
            System.out.println(result.get());
        }

        // Shutdown the executor service
        executor.shutdown();
    }
}

Output

Task 1 executed.
Task 2 executed.
Task 3 executed.

Explanation

  • Multiple Tasks: By using lambda expressions, multiple Callable tasks are easily defined and executed. Each task is submitted to an ExecutorService for parallel execution.
  • Fixed Thread Pool: The Executors.newFixedThreadPool(3) method is used to create a thread pool with a fixed number of threads, allowing multiple tasks to be executed concurrently.

Conclusion

Implementing Callable with lambda expressions in Java 8 simplifies the process of defining and executing tasks that return results or throw exceptions. By reducing boilerplate code and improving readability, lambda expressions make it easier to manage concurrent tasks in your applications. Whether you’re implementing a single Callable or managing multiple tasks, lambda expressions provide a clean and concise way to work with functional interfaces like Callable. Additionally, using ExecutorService allows you to execute these tasks efficiently, making your code more scalable and maintainable.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top