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
CallableWithout Lambda Expressions - Example 2: Implementing
Callablewith Lambda Expressions - Example 3: Executing Multiple
CallableTasks UsingExecutorService
- Example 1: Implementing
- 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
Callablein a concise and readable way.
Solution Steps
- Implement
CallableUsing Anonymous Classes: Demonstrate the traditional way of implementingCallablewithout lambda expressions. - Simplify with Lambda Expressions: Use lambda expressions to implement
Callablemore concisely. - Execute Multiple
CallableTasks: UseExecutorServiceto execute multipleCallabletasks 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
Callableinterface 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
Callabletasks are easily defined and executed. Each task is submitted to anExecutorServicefor 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.