Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

Concurrency Enhancements in Java 8

CompletableFuture

Java 8 introduced the CompletableFuture class as part of the java.util.concurrent package. It represents a future result of an asynchronous computation and provides a variety of methods to create, combine, and process asynchronous tasks in a functional style.

Creating a CompletableFuture

You can create a CompletableFuture using the CompletableFuture class's static factory methods.

Example: Creating a CompletableFuture

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureCreationExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // Creating an already completed CompletableFuture
        CompletableFuture completedFuture = CompletableFuture.completedFuture("Hello, World!");
        System.out.println("Completed Future: " + completedFuture.get());

        // Creating a CompletableFuture that runs asynchronously
        CompletableFuture asyncFuture = CompletableFuture.runAsync(() -> {
            System.out.println("Running asynchronously");
        });

        // Waiting for the asyncFuture to complete
        asyncFuture.get();
    }
} 

Chaining CompletableFuture

CompletableFuture allows you to chain multiple tasks together using methods like thenApply, thenAccept, and thenRun.

Example: Chaining CompletableFuture

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureChainingExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture future = CompletableFuture.supplyAsync(() -> "Hello")
                .thenApply(greeting -> greeting + ", World!")
                .thenApply(message -> message + " Welcome to CompletableFuture.");
        
        System.out.println("Result: " + future.get());
    }
} 

Combining CompletableFutures

Multiple CompletableFuture instances can be combined using methods like thenCombine and thenCompose.

Example: Combining CompletableFutures

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureCombiningExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture future1 = CompletableFuture.supplyAsync(() -> "Hello");
        CompletableFuture future2 = CompletableFuture.supplyAsync(() -> "World");

        CompletableFuture combinedFuture = future1.thenCombine(future2, (f1, f2) -> f1 + ", " + f2);
        System.out.println("Combined Result: " + combinedFuture.get());
    }
} 

Handling Errors

CompletableFuture provides methods like exceptionally and handle to handle errors that may occur during the asynchronous computation.

Example: Handling Errors

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureErrorHandlingExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture future = CompletableFuture.supplyAsync(() -> {
            if (true) {
                throw new RuntimeException("Exception occurred!");
            }
            return "Hello, World!";
        }).exceptionally(ex -> "Error: " + ex.getMessage());

        System.out.println("Result: " + future.get());
    }
} 

Combining Results from Multiple CompletableFutures

You can combine the results of multiple CompletableFuture instances using methods like allOf and anyOf.

Example: Combining Results from Multiple CompletableFutures

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.List;
import java.util.stream.Collectors;

public class CompletableFutureAllOfExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        List> futures = List.of(
            CompletableFuture.supplyAsync(() -> "Task 1"),
            CompletableFuture.supplyAsync(() -> "Task 2"),
            CompletableFuture.supplyAsync(() -> "Task 3")
        );

        CompletableFuture allOfFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));

        CompletableFuture> allResults = allOfFuture.thenApply(v ->
            futures.stream()
                   .map(CompletableFuture::join)
                   .collect(Collectors.toList())
        );

        allResults.get().forEach(System.out::println);
    }
} 

Using CompletableFuture with Executors

You can provide an Executor to CompletableFuture to control the threading behavior.

Example: Using CompletableFuture with Executors

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class CompletableFutureWithExecutorsExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(2);

        CompletableFuture future = CompletableFuture.supplyAsync(() -> {
            return "Hello, World!";
        }, executor);

        System.out.println("Result: " + future.get());
        executor.shutdown();
    }
} 

Conclusion

The CompletableFuture class in Java 8 provides a powerful framework for building and combining asynchronous tasks. It offers a rich set of methods for task chaining, combining results, handling errors, and working with custom executors, making it an essential tool for modern concurrent programming in Java.