CompletableFuture 을 보기 전에, 먼저 Future 에 대해 알고 가면 좋을 것 같다.
비동기 작업을 수행할 수 있게 해줌하지만, Future에는 단점이 존재!
- 외부에서 future를 완료시킬 수 없다.
get의 타임아웃 설정으로만 완료가 가능했다.- 여러 가지의 비동기 작업을 효율적으로 불가능
➡️ 여러 개의 future을 조합하지 못하기 때문!
그래서 Java 8 버전 부터 나온 것이 CompletableFuture 이다.
Future을 외부에서 완료 시킬 수 있게 되었다.Future를 조합하여 새로운 Future를 만들 수 있게 되었다.➡️ CompletableFuture의 주요 특징들을 살펴보자.
supplyAsyncCompletableFuture.supplyAsync(() -> {
// 시간이 걸리는 작업
return "Result";
});
CompletableFuture.runAsync(() -> {
// 시간이 걸리는 작업
});
CompletableFuture.supplyAsync(() -> "Hello")
.thenApplyAsync(result -> result + " World");
// 1) CompletableFuture가 Hello를 리턴값으로 전달
// 2) thenApplyAsync를 통해 전달받은 값을 '변환'함
CompletableFuture.supplyAsync(() -> "Hello")
.thenAcceptAsync(result -> System.out.println(result));
// 1) CompletableFuture가 Hello를 리턴값으로 전달
// 2) thenAcceptAsync를 통해 전달받은 값을 'sout'의 인자로 사용
CompletableFuture.supplyAsync(() -> "Hello")
.thenRunAsync(() -> System.out.println("Done"));
// 1) CompletableFuture가 Hello를 리턴값으로 전달
// 2) thenRunAsync는 결과값을 사용하지 않고, 단순히 자기 할 일만 함
thenApplyAsync,thenAcceptAsync,thenRunAsync의 차이
thenApplyAsync: 전달받은 값을변환함thenAcceptAsync: 전달받은 값을사용함thenRunAsync: 전달받은 값을사용하지 않음
CompletableFuture.supplyAsync(() -> "Hello")
.thenComposeAsync(result -> CompletableFuture.supplyAsync(() -> result + " World"));
// 1) CompletableFuture가 Hello를 리턴값으로 전달
// 2) result값을 매개변수로, 새로운 CompletableFuture을 실행한다.
CompletableFuture<String> future1 =
CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 =
CompletableFuture.supplyAsync(() -> "World");
future1.thenCombineAsync(future2, (result1, result2) ->
result1 + " " + result2);
// 1) future1이 Hello를 리턴
// 2) future2가 World를 리턴
// 3) future1이 수행된 후, future2의 결과를 'combine' 한다.
// 4) 그 결과로 "Hello" + " " + "World" 가 리턴된다.
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
CompletableFuture<Void> allOf = CompletableFuture.allOf(future1, future2);
allOf.thenRunAsync(() -> System.out.println("All tasks completed"));
// 1) future1은 Task 1 을 리턴 [ 3초가 걸린다고 가정 ]
// 2) future2는 Task 2 를 리턴 [ 5초가 걸린다고 가정 ]
// 3) allOf는 future1, future2를 모두 실행
// 4) 모든 future가 완료되어야 끝나기 때문에 [ 총 실행 후, 5초 이후에 완료 ]
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
CompletableFuture<Object> anyOf = CompletableFuture.anyOf(future1, future2);
anyOf.thenAcceptAsync(result -> System.out.println("First completed task: " + result));
// 1) future1은 Task 1 을 리턴 [ 3초가 걸린다고 가정 ]
// 2) future2는 Task 2 를 리턴 [ 5초가 걸린다고 가정 ]
// 3) anyOf는 future1, future2를 모두 실행
// 4) 하나의 Future라도 완료되면 끝나기 때문에 [ 총 실행 후, 3초 이후에 완료 ]
anyOf와allOf의 차이
allOf: 모든 CompletableFuture가 끝나야 완료된다.anyOf: 하나의 CompletableFuture가 끝나면, 완료된다.
thenApply(Function<? super T,? extends U> fn)CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(result -> result + " World")
.thenAccept(System.out::println);
// 1) supplyAsync()로 "Hello" 값이 리턴됨
// 2) thenApply()안에 새로운 값[ result + "World" ] 를 리턴하는 콜백 함수 설정
// 3) thenAccept()를 통해 해당 값 출력
thenAccept(Consumer<? super T> action)CompletableFuture.supplyAsync(() -> "Hello")
.thenAccept(result -> System.out.println(result));
// 1) supplyAsync()를 통해 "Hello" 값이 리턴됨
// 2) thenAccept()를 통해 전달받은 Hellow값을 출력하는 콜백 함수 수행
thenRun(Runnable action)CompletableFuture.supplyAsync(() -> "Hello")
.thenRun(() -> System.out.println("Done"));
// 1) supplyAsync()를 통해 "Hello" 값 리턴
// 2) 결과를 사용하지 않고, 해당 작업이 완료되면 "Done"을 출력하는 콜백 함수 수행
thenCompose(Function<? super T,? extends CompletionStage<U>> fn)CompletableFuture.supplyAsync(() -> "Hello")
.thenCompose(result -> CompletableFuture.supplyAsync(() -> result + " World"))
.thenAccept(System.out::println);
// 1) supplyAsync()로 "Hello" 결과값 리턴
// 2) 결과값 (result) 를 가지고, 새로운 비동기 작업 시작
// 3) 새로운 비동기 작업 수행 [ result + " " + "World" ]
// 4) 결과값으로 "Hello World"가 accept되어 출력됨
thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
future1.thenCombine(future2, (result1, result2) -> result1 + " " + result2)
.thenAccept(System.out::println);
// 1) future1은 "Hello" 리턴
// 2) future2는 "World" 리턴
// 3) future1가 수행되고, future2의 결과값을 가지고 combine 수행
// 4) combine 콜백 메소드는 [ result1 + " " + result2 ]
// 5) 결과값으로 "Hello World" 가 출력됨
thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action)CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
future1.thenAcceptBoth(future2, (result1, result2) -> System.out.println(result1 + " " + result2));
// 1) future1은 "Hello"를 리턴
// 2) future2는 "World"를 리턴
// 3) future1이 수행되고, future2의 결과값을 가지고 AcceptBoth 수행
// 4) 콜백 함수는 [ result1 + " " + result2 ] 값을 sout 하는 것
// 5) 결과로 "Hellow World"가 출력됨
runAfterBoth(CompletionStage<?> other, Runnable action)CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
future1.runAfterBoth(future2, () -> System.out.println("Both tasks completed"));
// 1) future1은 "Hello"를 리턴
// 2) future2는 "World"를 리턴
// 3) future1이 수행되고, future2가 완료되면 콜백 함수 수행
// 4) 아무런 결과값도 사용하지 않고 그냥 콜백 함수가 수행됨
// 5) 결과값은 "Both tasks completed"
applyToEither(CompletionStage<? extends T> other, Function<? super T,? extends U> fn)CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
future1.applyToEither(future2, result -> "First completed: " + result)
.thenAccept(System.out::println);
// 1) future1은 "Task1"을 리턴 [ 5초가 걸린다고 가정 ]
// 2) future2는 "Task2"를 리턴 [ 3초가 걸린다고 가정 ]
// 3) future2 [ 3s ] 가 먼저 완료되므로, result 값에는 "Task 2"가 들어간다.
// 4) 결과는 "First completed : Task 2" 가 된다.
acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
future1.acceptEither(future2, result -> System.out.println("First completed: " + result));
// 1) future1은 "Task 1"을 리턴 [ 3초가 걸린다고 가정 ]
// 2) future2는 "Task 2"를 리턴 [ 5초가 걸린다고 가정 ]
// 3) future1 [ 3s ] 가 먼저 완료되므로, result 값에는 "Task 1"이 들어간다.
// 4) 그 값을 바로 콜백 함수에서 사용하므로, 결과값은 "Frist Completed : Task 1" 이 된다.
runAfterEither(CompletionStage<?> other, Runnable action)CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Task 1");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "Task 2");
future1.runAfterEither(future2, () -> System.out.println("One of the tasks completed"));
// 1) future1은 "Task 1"을 리턴 [ 3초가 걸린다고 가정 ]
// 2) future2는 "Task 2"를 리턴 [ 5초가 걸린다고 가정 ]
// 3) future1 [ 3s ] 가 먼저 완료되므로, 시작 후 '3초' 뒤에 runAfterEither가 수행됨.
// 4) 결과값을 사용하지 않고, 단순히 자기 자신의 콜백 함수를 수행한다.
// 5) 결과값은 : "One of the tasks completed" 가 된다.
exceptionally(Function<Throwable,? extends T> fn)CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Error");
}).exceptionally(ex -> {
System.out.println("Exception: " + ex.getMessage());
return "Fallback result";
}).thenAccept(System.out::println);
// 1) supplyAsync() 수행 중, 'RuntimeException' 발생
// 2) exceptionally에서 해당 Exception을 출력
// 3) 후에, return 값으로 "Fallback result" 값 리턴
// 4) thenAccept를 통해 에러 결과값 출력
handle(BiFunction<? super T,Throwable,? extends U> fn)CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Error");
}).handle((result, ex) -> {
if (ex != null) {
System.out.println("Exception: " + ex.getMessage());
return "Fallback result";
}
return result;
}).thenAccept(System.out::println);
// 1) supplyAsync() 수행 중, 'RuntimeException' 발생
// 2) if 조건문 수행
// 3) if [ exception이 없다면 ] ➡️ result 리턴
// 4) else ➡️ Exception 처리
// 5) 현재 예시에서 결과값은 "Exception: Error", "Fallback result"가 된다.
이렇게 예제들과 함께 자바 비동기 프로그래밍을 위한 CompletableFuture을 알아보았다.
함수형 프로그래밍이 아직 낯설지만, 열시미 해야쥐