반응형

비동기 작업에서 Executor를 사용하는 방법은 CompletableFuture의 비동기 작업을 특정 Executor를 통해 실행하도록 설정하는 것입니다. 기본적으로 CompletableFuture.supplyAsyncCompletableFuture.runAsync는 공용 ForkJoinPool의 공용 스레드 풀을 사용하지만, 특정 Executor를 지정하여 사용자 정의 스레드 풀을 사용할 수도 있습니다.

 

Executor를 사용하는 방법

 

1. Executor 생성

 

먼저, 사용할 Executor를 생성해야 합니다. 예를 들어, ExecutorService는 자주 사용되는 Executor 구현 중 하나입니다.

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

ExecutorService executor = Executors.newFixedThreadPool(10);

2. CompletableFuture에서 Executor 사용

 

CompletableFuture의 비동기 작업을 시작할 때, Executor를 두 번째 인수로 전달합니다.

 

supplyAsync 사용 예

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

public class CompletableFutureWithExecutorExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(10);

        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            // 비동기 작업 수행
            return "Hello, World!";
        }, executor);

        future.thenAccept(result -> System.out.println("Result: " + result));

        // Executor 서비스 종료
        executor.shutdown();
    }
}

3. 여러 비동기 작업을 결합하여 Executor 사용

thenApplyAsync, thenAcceptAsync, thenRunAsync 등의 메서드도 Executor를 인수로 받아서 지정할 수 있습니다.

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

public class CompletableFutureWithMultipleAsyncExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(10);

        CompletableFuture.supplyAsync(() -> {
            // 첫 번째 비동기 작업
            return "Hello";
        }, executor).thenApplyAsync(result -> {
            // 두 번째 비동기 작업
            return result + ", World!";
        }, executor).thenAcceptAsync(result -> {
            // 세 번째 비동기 작업
            System.out.println("Result: " + result);
        }, executor);

        // Executor 서비스 종료
        executor.shutdown();
    }
}

 

요약

 

1. Executor 생성: ExecutorService와 같은 Executor 구현체를 생성합니다.

ExecutorService executor = Executors.newFixedThreadPool(10);

2. 비동기 작업에서 Executor 사용: supplyAsync, runAsync, thenApplyAsync, thenAcceptAsync, thenRunAsync 등의 메서드에서 Executor를 두 번째 인수로 전달합니다.

CompletableFuture.supplyAsync(() -> "Hello, World!", executor);

3. Executor 서비스 종료: 모든 비동기 작업이 완료되면 executor.shutdown()을 호출하여 Executor를 종료합니다.

executor.shutdown();

 

CompletableFuture Executor

CompletableFutureExecutor를 같이 사용하는 것은 여러 가지 이점을 제공합니다.

이 조합을 사용하면 비동기 작업의 효율성과 유연성을 극대화할 수 있습니다.

주요 이점

 

1. 병렬 처리의 효율성 증가

 

설명: 여러 비동기 작업을 병렬로 처리함으로써 전체 작업의 수행 시간을 단축할 수 있습니다. Executor를 사용하면 특정 스레드 풀을 통해 작업을 분산시켜 병렬 처리의 효율성을 극대화할 수 있습니다.

 

2. 작업 스케줄링 제어

 

설명: Executor를 사용하면 비동기 작업의 실행 정책을 세밀하게 제어할 수 있습니다. 예를 들어, 고정된 수의 스레드 풀, 캐시된 스레드 풀, 단일 스레드 풀 등을 사용하여 작업을 스케줄링할 수 있습니다.

 

3. 리소스 관리

 

설명: Executor를 사용하면 스레드 생성 및 관리를 중앙집중식으로 제어할 수 있어 시스템 리소스를 효율적으로 관리할 수 있습니다. 이를 통해 과도한 스레드 생성으로 인한 성능 저하를 방지할 수 있습니다.

 

4. 작업의 독립성 보장

 

설명: 각 비동기 작업을 별도의 스레드에서 실행하므로 작업 간의 간섭을 최소화할 수 있습니다. 이를 통해 독립적인 작업이 서로 영향을 주지 않고 안전하게 실행될 수 있습니다.

CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
    // 첫 번째 작업
}, executor);
CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
    // 두 번째 작업
}, executor);

5. 복잡한 비동기 작업 처리

 

설명: 여러 비동기 작업을 조합하여 복잡한 비동기 워크플로우를 쉽게 구현할 수 있습니다. CompletableFuture의 다양한 메서드를 사용하여 작업 간의 의존성을 설정하고, Executor를 통해 이러한 작업을 효율적으로 처리할 수 있습니다.

 

6. 예외 처리 및 복구

 

설명: 비동기 작업에서 발생하는 예외를 처리하고, 필요시 복구 작업을 수행할 수 있습니다. CompletableFutureexceptionally, handle 메서드와 함께 사용하여 예외 처리를 더 유연하게 할 수 있습니다.

 

종합 예시 코드

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

public class CompletableFutureWithExecutorExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(10);

        CompletableFuture.supplyAsync(() -> {
            // 비동기 작업 1
            return "Task 1 result";
        }, executor).thenApplyAsync(result -> {
            // 비동기 작업 2
            return result + " + Task 2 result";
        }, executor).thenAcceptAsync(result -> {
            // 비동기 작업 3
            System.out.println("Final Result: " + result);
        }, executor).exceptionally(ex -> {
            System.err.println("Exception: " + ex);
            return null;
        });

        executor.shutdown();
    }
}

 

요약

기본 스레드 풀: CompletableFutureExecutor와 함께 사용하지 않으면, 기본적으로 ForkJoinPool의 공용 스레드 풀을 사용합니다.

공용 스레드 풀의 크기는 시스템의 가용 프로세서 수에 따라 자동으로 결정됩니다.

반응형
반응형

CompletableFuture는 자바 8에서 도입된 java.util.concurrent 패키지의 클래스입니다. 비동기 프로그래밍을 쉽게 구현할 수 있도록 다양한 메서드와 기능을 제공합니다. CompletableFuture는 비동기 작업을 수행하고, 그 결과를 비동기적으로 처리할 수 있게 해줍니다.

 

1. CompletableFuture의 기본 개념

 

비동기 프로그래밍: 메인 스레드와는 별도로 작업을 수행하여 응답성을 높입니다.

비동기 작업의 관리: 작업의 완료 여부를 확인하고, 작업이 완료되면 후속 작업을 수행합니다.

콜백 등록: 작업이 완료되면 실행할 콜백 함수를 등록할 수 있습니다.

 

2. CompletableFuture의 생성

 

CompletableFuture 객체는 여러 가지 방법으로 생성할 수 있습니다.

 

직접 생성:

CompletableFuture<String> future = new CompletableFuture<>();

 

비동기 작업을 시작하면서 생성:

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

 

비동기 작업의 결과를 미리 정의하면서 생성:

CompletableFuture<String> future = CompletableFuture.completedFuture("Hello, World!");

 

3. 주요 메서드

 

비동기 작업 실행

 

runAsync: 결과가 없는 작업을 비동기로 실행합니다.

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    System.out.println("Running asynchronously");
});

 

supplyAsync: 결과가 있는 작업을 비동기로 실행합니다.

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

 

결과 처리

 

thenApply: 이전 작업의 결과를 받아서 변환합니다.

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
                                                    .thenApply(result -> result + ", World!");

thenAccept: 이전 작업의 결과를 받아서 소비합니다.

CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> "Hello")
                                                  .thenAccept(result -> System.out.println(result));

thenRun: 이전 작업의 결과를 사용하지 않고 실행할 작업을 정의합니다.

CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> "Hello")
                                                  .thenRun(() -> System.out.println("Task completed"));

예외 처리

 

exceptionally: 예외가 발생했을 때 대체 값을 제공합니다.

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    if (true) throw new RuntimeException("Error occurred");
    return "Hello";
}).exceptionally(ex -> "Recovered from error");

 

handle: 정상적인 결과와 예외를 모두 처리합니다.

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    if (true) throw new RuntimeException("Error occurred");
    return "Hello";
}).handle((result, ex) -> {
    if (ex != null) return "Recovered from error";
    return result;
});

 

여러 작업의 조합

 

thenCombine: 두 비동기 작업의 결과를 조합합니다.

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> result1 + " " + result2);

allOf: 여러 비동기 작업을 모두 완료할 때까지 기다립니다.

CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(future1, future2);

anyOf: 여러 비동기 작업 중 하나라도 완료되면 결과를 반환합니다.

CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(future1, future2);

4. CompletableFuture 사용 예제

 

예제 1: 기본 사용

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExample {
    public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
                                                            .thenApply(result -> result + ", World!")
                                                            .thenAccept(System.out::println);
    }
}

예제 2: 예외 처리

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExceptionExample {
    public static void main(String[] args) {
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            if (true) throw new RuntimeException("Error occurred");
            return "Hello";
        }).exceptionally(ex -> "Recovered from error")
          .thenAccept(System.out::println);
    }
}

예제 3: 여러 작업의 조합

import java.util.concurrent.CompletableFuture;

public class CompletableFutureCombineExample {
    public static void main(String[] args) {
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");

        CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> result1 + " " + result2);
        combinedFuture.thenAccept(System.out::println);
    }
}

 

요약

 

비동기 프로그래밍: CompletableFuture는 비동기 작업을 쉽게 관리하고 처리할 수 있는 다양한 기능을 제공합니다.

비동기 작업 실행: runAsyncsupplyAsync를 사용하여 비동기 작업을 시작합니다.

결과 처리: thenApply, thenAccept, thenRun 등의 메서드를 사용하여 비동기 작업의 결과를 처리합니다.

예외 처리: exceptionally, handle 메서드를 사용하여 예외를 처리합니다.

여러 작업의 조합: thenCombine, allOf, anyOf를 사용하여 여러 비동기 작업을 조합합니다.

반응형
반응형

중간 연산은 스트림 파이프라인에서 데이터를 변환하고 필터링하는 작업을 수행합니다.

중간 연산은 지연 연산(lazy evaluation)으로, 최종 연산이 호출될 때까지 실제로 수행되지 않습니다. 중간 연산은 항상 새로운 스트림을 반환합니다.

 

1. filter

설명: 주어진 조건에 맞는 요소만을 포함하는 스트림을 반환합니다.

예시:

List<String> items = Arrays.asList("Apple", "Banana", "Orange");
Stream<String> filteredStream = items.stream().filter(item -> item.startsWith("A"));

2. map

설명: 각 요소를 주어진 함수에 의해 변환된 결과로 매핑하여 새로운 스트림을 반환합니다.

예시:

List<String> items = Arrays.asList("Apple", "Banana", "Orange");
Stream<String> mappedStream = items.stream().map(String::toUpperCase);

3. flatMap

설명: 각 요소를 스트림으로 변환한 후, 하나의 스트림으로 평탄화하여 반환합니다.

예시:

List<List<String>> listOfLists = Arrays.asList(
    Arrays.asList("a", "b", "c"),
    Arrays.asList("d", "e", "f")
);
Stream<String> flatMappedStream = listOfLists.stream().flatMap(List::stream);

4. distinct

설명: 스트림의 중복 요소를 제거합니다.

예시:

List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);
Stream<Integer> distinctStream = numbers.stream().distinct();

5. sorted

설명: 스트림의 요소를 정렬합니다.

예시:

List<String> items = Arrays.asList("Banana", "Apple", "Orange");
Stream<String> sortedStream = items.stream().sorted();

6. peek

설명: 각 요소를 소비하는 동안 추가 작업을 수행합니다. 주로 디버깅 목적으로 사용됩니다.

예시:

List<String> items = Arrays.asList("Apple", "Banana", "Orange");
Stream<String> peekedStream = items.stream().peek(System.out::println);

7. limit

설명: 스트림의 요소를 지정된 수만큼 제한합니다.

예시:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> limitedStream = numbers.stream().limit(3);

8. skip

설명: 스트림의 처음 N개의 요소를 건너뜁니다.

예시:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> skippedStream = numbers.stream().skip(2);

 

최종 연산 (Terminal Operations)

 

최종 연산은 스트림 파이프라인을 실행하고 결과를 생성합니다. 최종 연산은 스트림을 소비하며, 더 이상 다른 스트림 연산을 수행할 수 없습니다.

 

1. forEach

설명: 각 요소를 소비하여 주어진 작업을 수행합니다.

예시:

List<String> items = Arrays.asList("Apple", "Banana", "Orange");
items.stream().forEach(System.out::println);

2. collect

설명: 스트림의 요소를 컬렉션이나 다른 유형의 결과로 수집합니다.

예시:

List<String> items = Arrays.asList("Apple", "Banana", "Orange");
List<String> collectedList = items.stream().collect(Collectors.toList());

 

3. reduce

설명: 스트림의 요소를 결합하여 하나의 값으로 줄입니다.

예시:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, Integer::sum);

 

4. count

설명: 스트림의 요소 개수를 반환합니다.

예시:

List<String> items = Arrays.asList("Apple", "Banana", "Orange");
long count = items.stream().count();

 

5. anyMatch, allMatch, noneMatch

설명: 스트림의 요소가 주어진 조건에 대해 하나라도/모두/하나도 만족하지 않는지 확인합니다.

예시:

List<String> items = Arrays.asList("Apple", "Banana", "Orange");
boolean anyMatch = items.stream().anyMatch(item -> item.startsWith("A"));
boolean allMatch = items.stream().allMatch(item -> item.length() > 3);
boolean noneMatch = items.stream().noneMatch(item -> item.startsWith("Z"));

6. findFirst, findAny

설명: 스트림의 첫 번째 요소를 찾거나, 임의의 요소를 찾습니다.

예시:

List<String> items = Arrays.asList("Apple", "Banana", "Orange");
Optional<String> firstItem = items.stream().findFirst();
Optional<String> anyItem = items.stream().findAny();

7. toArray

설명: 스트림의 요소를 배열로 반환합니다.

예시:

List<String> items = Arrays.asList("Apple", "Banana", "Orange");
String[] itemArray = items.stream().toArray(String[]::new);

 

요약

 

중간 연산: 스트림을 변환하거나 필터링하는 연산으로, 지연 연산(lazy evaluation) 특성을 가집니다. (예: filter, map, distinct, sorted)

최종 연산: 스트림을 소비하여 결과를 생성하는 연산으로, 스트림 파이프라인을 실행합니다. (예: forEach, collect, reduce, count)

반응형
반응형

스트림 API

 

스트림 API의 필요성

 

함수형 프로그래밍 지원: 자바 8에서 도입된 스트림 API는 함수형 프로그래밍을 지원하여 코드의 간결성과 가독성을 높입니다.

데이터 처리 효율성: 컬렉션 데이터의 필터링, 매핑, 정렬 등 다양한 작업을 쉽게 수행할 수 있습니다.

병렬 처리: 스트림 API는 간단한 병렬 처리를 지원하여 대량 데이터 처리의 성능을 향상시킵니다.

 

스트림 API의 기본 사용법

 

스트림 생성

  - 컬렉션에서 스트림 생성:

List<String> items = Arrays.asList("Apple", "Banana", "Orange");
Stream<String> stream = items.stream();

 

 - 배열에서 스트림 생성:

String[] array = {"Apple", "Banana", "Orange"};
Stream<String> stream = Arrays.stream(array);

 

중간 연산 (Intermediate Operations)

  - 필터링 (Filtering):

Stream<String> filteredStream = stream.filter(item -> item.startsWith("A"));

 

  - 매핑 (Mapping):

Stream<String> mappedStream = stream.map(String::toUpperCase);

 

최종 연산 (Terminal Operations)

  - 수집 (Collecting):

List<String> result = stream.collect(Collectors.toList());

 

  - 반복 (Iteration):

stream.forEach(System.out::println);

 

람다 표현식의 필요성

 

간결한 코드: 람다 표현식은 익명 함수를 표현하는 간결한 방법으로, 코드의 길이를 줄이고 가독성을 높입니다.

함수형 프로그래밍: 함수형 인터페이스와 함께 사용되어 함수형 프로그래밍 패턴을 지원합니다.

 

람다 표현식의 기본 사용법

 

기본 문법: (매개변수) -> { 함수 내용 }

(int a, int b) -> a + b;

단순 예제:

Runnable 인터페이스 구현:

Runnable r = () -> System.out.println("Hello, World!");
new Thread(r).start();

함수형 인터페이스와 함께 사용:

Predicate 인터페이스:

Predicate<String> isEmpty = s -> s.isEmpty();
boolean result = isEmpty.test("");

 

실습 예제 1: 리스트 필터링과 매핑

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExample {
    public static void main(String[] args) {
        List<String> items = Arrays.asList("Apple", "Banana", "Orange", "Apricot");

        // 필터링: "A"로 시작하는 항목 필터링
        List<String> filteredItems = items.stream()
                                          .filter(item -> item.startsWith("A"))
                                          .collect(Collectors.toList());
        System.out.println("Filtered Items: " + filteredItems);

        // 매핑: 모든 항목을 대문자로 변환
        List<String> mappedItems = items.stream()
                                        .map(String::toUpperCase)
                                        .collect(Collectors.toList());
        System.out.println("Mapped Items: " + mappedItems);
    }
}

실습 예제 2: 숫자 리스트 처리

import java.util.Arrays;
import java.util.List;

public class LambdaExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 스트림을 사용하여 합계 계산
        int sum = numbers.stream()
                         .reduce(0, (a, b) -> a + b);
        System.out.println("Sum: " + sum);

        // 람다 표현식을 사용하여 각 숫자를 출력
        numbers.forEach(n -> System.out.println("Number: " + n));
    }
}

 

 

스트림의 특징
1. Stream 은 원본의 데이터를 변경하지 않는다. 

2. Stream 은 재 사용이 불가능하여서 일회용으로 사용된다.

3. 내부 반복으로 작업을 처리한다.

반응형
반응형

자바의 Integer와 int 자료형 차이

 

1. 기본 데이터 타입 (Primitive Type) - int

 

정의: int는 기본 데이터 타입으로, 32비트 정수를 저장합니다.

특징:

메모리 효율적: 4바이트(32비트)를 사용하여 정수 값을 저장합니다.

값 저장: 단순히 값을 저장하고 연산을 수행합니다.

기본값: 0 (선언만 하고 초기화하지 않은 경우)

성능: 오토박싱/언박싱이 필요 없기 때문에 성능이 더 좋습니다.

 

2. 참조 데이터 타입 (Reference Type) - Integer

 

정의: Integer는 자바의 래퍼 클래스(wrapper class)로, int의 객체 표현입니다.

특징:

클래스 기반: Integer 클래스는 java.lang 패키지에 속하며, 객체로 취급됩니다.

기본값: null (선언만 하고 초기화하지 않은 경우)

기능: 추가 메서드와 기능 제공 (예: parseInt, valueOf, toString 등)

오토박싱 및 언박싱: 자바 컴파일러가 기본 타입과 래퍼 클래스 간의 자동 변환을 수행합니다.

 

Integer x = 10; // 오토박싱
int y = x;      // 언박싱
Integer z = Integer.valueOf(20);
int sum = x + z;

3. 차이점 요약

 

4. 언제 사용해야 하나?

int 사용 시점:

- 성능이 중요한 경우

- 단순히 숫자 값을 저장하고 연산할 때

Integer 사용 시점:

- 객체로 처리해야 할 때 (예: 컬렉션 프레임워크와 함께 사용)

- null 값을 허용해야 할 때

- 메서드와 추가 기능이 필요할 때

 

 

5. 기본 데이터 타입과 래퍼 클래스의 다른 예

 

자바에는 다양한 기본 데이터 타입과 그에 대응하는 래퍼 클래스가 있습니다. 각 래퍼 클래스는 기본 데이터 타입을 객체로 다룰 수 있도록 하며, 추가적인 메서드와 기능을 제공합니다.

 

 기본 데이터 타입과 그에 대응하는 래퍼 클래스

 

요약

 

기본 데이터 타입은 메모리 효율적이고 성능이 좋지만, 객체로 다룰 수 없으며 null 값을 가질 수 없습니다. 반면 래퍼 클래스는 추가적인 메서드와 기능을 제공하며 컬렉션과 같은 객체 기반 데이터 구조에서 사용할 수 있습니다. 따라서 상황에 따라 적절한 타입을 선택하는 것이 중요합니다.

반응형
반응형

Java 8 Optional

optional 은 java.util 패키지의 Java 8에 추가된 새로운 클래스입니다. 이 도움말에서는 Java Optional 클래스와 해당 개체에 대해 자세히 설명합니다.

 
Optional의 필요성
간단히 말해서, Optional은 Java에서 NullPointerException을 우아하게 다루기 위해 설계되었습니다.
아마도 null 참조에 익숙하실 겁니다. 이로 인해 가장 흔하게 발생하는 NullPointerException을 아래와 같이 보실 수 있습니다.
String str = null;
if(str.equals("codippa")) {
// do something
}
 
 

null 참조에서 메서드를 호출하려고 하기 때문에 java.lang.NullPointerException을 발생시킵니다. 이 문제를 해결하기 위해 Optional 클래스가 추가되었습니다.

 

간단한 null 체크로 NullPointerException을 피할 수 있다고 생각할 수 있습니다. 아래 코드와 같이 말이죠.

 

String str = null;
if(str != null && str.equals("codippa")) {
// 
}

그렇다면 왜 이를 위한 별도의 클래스가 필요한 걸까요?
맞습니다. 하지만 아래 코드를 살펴보세요.

 

String country = student.getStudentDetails().getAddress().getCountry().toLowerCase();
switch(country) {
case "India":
// 뭔가를 수행합니다
case "USA":
// 뭔가를 수행합니다

// 다른 나라들
}
 

위 예제는 student, studentDetails, address 또는 country 객체 중 하나라도 null이라면 NullPointerException의 가능성이 여러 곳에서 발생할 수 있습니다.

이제 여러분은 이전처럼 null 체크를 추가하고 싶을지도 모릅니다. 그러나 그렇게 한다면 위 코드는 다음과 같이 변합니다.

 

String country = null;

if(student != null) {
	StudentDetails studentDetails = student.getStudentDetails();
	if(studentDetails() != null) {
		Address address = studentDetails().getAddress();
		if(address() != null) {
			Country studentCountry = address.getCountry();
			if(studentCountry != null) {
				country = studentCountry.toLowerCase();
			}
		}
	}
}
 

보시는 대로 이 코드는 읽기 어렵고 유지 및 수정하기도 어렵습니다. 이러한 문제를 해결하기 위해 Optional이 만들어졌으며, Optional에 대해 자세히 학습한 후에 다시 살펴보겠습니다.


Optional이란?
Optional은 객체 위에 래퍼 또는 컨테이너입니다. 값(또는 객체)을 포함할 수도 있고 포함하지 않을 수도 있습니다.
Optional은 어떤 객체든 포함할 수 있으므로 Optional<String>, Optional<Cat> 등의 형식으로 선언되며 아래와 같이 생겼습니다.


Optional을 사용하면 값이 존재할 때만 해당 값을 가져오거나, 보유하는 객체가 없거나 null인 경우 기본값을 반환할 수 있습니다.

이를 통해 NullPointerPointerException 문제를 해결하며 코드를 기존의 null 체크보다 더 읽기 쉽게 만듭니다. Optional을 깊게 학습한 후에 위의 예제를 Optional 객체를 사용하여 어떻게 수정할 수 있는지 살펴보겠습니다.

Optional 객체 생성
Optional 객체는 생성자를 통해 생성할 수 없습니다. 이는 생성자가 private이기 때문입니다.
Optional 객체는 정적 메서드를 사용하여만 생성할 수 있으며 아래에 설명된대로 생성됩니다.

 

1. of() 메서드 사용
Optional은 Optional.of() 메서드를 호출하여 해당 Optional이 보유할 객체를 제공하여 생성할 수 있습니다.
예제,

Student student = new Student();
Optional<Student> optional = Optional.of(student);
만약 of에 제공된 객체가 null이면 NullPointerException이 발생합니다. 객체가 null일 가능성이 있는 경우에는 다음에 설명하는 메서드를 사용하세요.

 

2. ofNullable() 메서드 사용
이것은 Optional 객체를 생성하는 두 번째 방법입니다.
ofNullable()도 정적 메서드이며 인수로 객체를 받아 이 객체를 포함하는 Optional을 생성합니다.

만약 객체가 null이라면 빈 Optional 객체를 생성하고 반환합니다. 예제,

Student student = new Student();
Optional<Student> optional = Optional.ofNullable(student);

 

 

 

 

3. 빈 optional 객체 생성
아래와 같이 정적 빈 메소드를 사용하여 빈 값을 가진 Optional 객체를 생성하는 것도 가능합니다.

Optional<Student> optional = Optional.empty(student);

공백으로 반환된 Optional 은 모든 참조 유형에 할당 될 수 있습니다. 

즉 Optional<String>, Optional<Shape>, Optional<Object>


Value of Optional
앞에서 설명한 것처럼 Optional은 객체를 둘러싼 래퍼이지만 기본 객체에 어떻게 액세스합니까? Optional은 래핑하는 객체를 반환하는 get 메소드를 제공합니다. 이 객체의 유형은 Optional 유형과 동일합니다.

 

String color = "red";
Optional<String> optional = Optional.of(color);
String back = optional.get();
System.out.print("Color is : " + back);

결과

 

Color is : red

Check Optional value
Optional 객체에 값이 포함되어 있지 않은 경우, 즉 그것이 empty() 또는 ofNullable() 메서드를 사용하여 생성된 빈 Optional이고 get()을 사용하여 해당 값에 액세스하려고 하면 오류가 발생합니다.

Exception in thread “main” java.util.NoSuchElementException: No value present

이 오류를 방지하려면 isPresent() 또는 isEmpty() 메서드를 사용하여 Optional에 액세스하기 전에 Optional에 값이 포함되어 있는지 확인하는 것이 좋습니다.
isPresent()
이 메서드는 Optional에 값이 포함되어 있으면 true를 반환하고 그렇지 않으면 false를 반환합니다. 

 
Optional<String> optional = Optional.empty();
System.out.print("Optional contains a value? " + optional.isPresent());

결과

Optional contains a value? false

isEmpty()
이 메소드는 isPresent()와 반대로 작동하며 Optional 객체에 값이 없으면 true를 반환하고 그렇지 않으면 false를 반환합니다.

 

// create empty Optional value
Optional<String> optional = Optional.empty();
System.out.print("Optional contains a value? " + optional.isEmpty());

결과

Optional contains a value? true

Action on value present
마지막 섹션에서는 Optional에 값이 포함되어 있는지 확인하는 방법을 살펴보았습니다.
Optional에 값이 포함된 경우 일부 작업을 수행하려면 다음 줄을 생각해 보세요.

Optional<String> o = Optional.empty();
// check if optional contains a value
if(o.isPresent()) {
System.out.print(o.get());
}

그러나 Optional은 if 조건이 필요하지 않은 단축 방법을 제공합니다.

Optional의 isPresent() 메소드를 사용합니다.

 

이 메소드는 기능적 인터페이스인 java.util.function.Consumer 유형의 인수를 허용하므로 해당 구현을 Lambda 표현식으로 직접 제공할 수 있습니다.

 

Optional<String> o = Optional.of("Learning optional");
// print value of optional if non-empty
o.ifPresent((e) -> System.out.println(o.get()));

결과

Learning optional

 

Default optional value
위의 예는 Optional에 값이 있는 경우 취해야 할 조치와 값이 없는 경우 취해야 할 조치를 보여줍니다.
Optional은 비어 있는 경우 일부 작업을 수행하는 방법을 제공합니다.
이러한 방법은
1. orElse()
이 메서드는 값을 인수로 받아들이고 Optional이 비어 있으면 이 값을 반환합니다.
이 인수의 유형은 Optional 유형과 동일해야 합니다.

// simple pizza
Pizza plainPizza = new Pizza();
plainPizza.setDescription("Pizza without any toppings");
// create empty Optional
Optional<Pizza> empty = Optional.empty();
// get pizza from optional
Pizza p = empty.orElse(plainPizza);
System.out.print(p.getDescription())

이 예제에서는 빈 Optional을 생성한 다음 기본 객체를 인수로 사용하여 orElse() 메서드를 호출합니다. 이 코드의 출력은 다음과 같습니다

Pizza without any toppings

이는 orElse()가 반환한 객체가 해당 인수와 동일함을 보여줍니다.
2. orElseGet()
이 메서드는 orElse()와 유사하며 Optional이 비어 있으면 값을 반환합니다.
이는 java.util.function.Supplier 유형의 인수를 허용하므로 공급자가 반환한 값을 반환합니다.

orElseGet()의 예가 앞서 제공됩니다.

// create empty optional
Optional<String> empty = Optional.empty();
String s = empty.orElseGet(() -> "Optional is empty");
System.out.println(s);

결과

Optional is empty

공급자가 기능적 인터페이스이기 때문에 orElseGet() 메서드에 대한 인수는 Lambda 표현식입니다.
위 구문이 혼란스러워 보인다면 orElseGet()에 대한 호출을 별도의 줄로 나누는 아래 코드를 살펴보세요.

 

// define a supplier
Supplier<String> supplier = () -> "Optional is empty";
String s = empty.orElseGet(supplier);
 

 

Difference between orElse() & orElseGet()
1. 두 메소드 사이의 눈에 띄는 차이점은 orElse()는 Optional 객체와 동일한 유형의 인수를 허용하는 반면 orElseGet() 메소드는 java.util.function.Supplier 유형의 인수를 허용한다는 것입니다.

2. 또 다른 차이점은 orElse()가 단순히 제공된 인수를 반환하는 반면 orElseGet()은 결과를 검색하기 위해 공급자 메서드를 호출한다는 것입니다.

3. 하지만 그 이면에는 또 다른 차이점이 있는데, 이 차이점은 아래 예에서 확인할 수 있습니다.

private void getData() {
System.out.println("Fetching data");
}

// create non-empty optional
Optional<String> o = Optional.of("Learning optional");
System.out.println("------------ orElse ------------");

// using orElse
String s = o.orElse(getData());
System.out.println(s);
System.out.println("\n------------orElseGet-------------");

// using orElseGet
s = o.orElseGet(() -> getData());
System.out.println(s);
 

orElse() 및 orElseGet()에 값을 직접 제공하는 대신 값을 반환하는 메서드를 호출합니다.

이 코드를 실행하면 다음과 같은 출력이 생성됩니다.

———— orElse ————
Fetching data
Learning optional

————orElseGet————-
Learning optional

이 출력에서 Optional이 비어 있지 않더라도
orElse()는 여전히 메서드를 실행하는 반면 orElseGet()은 Optional이 비어 있는 경우에만 메서드를 실행합니다.

이는 데이터를 가져오는 데 관련된 네트워크 또는 데이터베이스 호출이 있는 경우 상당히 중요합니다.
3. or()
이 메소드는 Java 9에 추가되었습니다. Optional에 값이 포함되어 있으면 동일한 Optional을 반환하고 그렇지 않으면 다른 기본 Optional 개체를 반환합니다.

or()는 java.util.function.Supplier 유형의 인수를 기대합니다. 반환된 기본 Optional은 이 인수 공급자 함수에 의해 제공됩니다.

String s = "optional value";
// create an optional
Optional<String> o = Optional.ofNullable(s);
// get Optional with or
Optional<String> or = o.or(()-> Optional.of("Default"));
System.out.println("Optional value:: " + or.get());

// create an optional with null value
Optional<String> empty = Optional.ofNullable(null);
// get Optional with or
Optional<String> defaultOptional = empty.or(()-> Optional.of("Default"));
System.out.println("Optional value:: " + defaultOptional.get());

결과

Optional value:: Optional value
Optional value:: Default

Optional에 값이 포함되어 있으면 또는 동일한 Optional을 반환합니다.
그러나 비어 있으면 or의 인수 함수에서 제공하는 Optional이 반환됩니다.

 

Throwing exceptions
지금까지 Optional이 비어 있는 경우 기본값을 반환하는 방법을 살펴보았습니다. 그러나 때로는 비어 있는 Optional을 오류로 간주해야 하는 경우도 있습니다.

이러한 상황을 처리하기 위해 Optional이 비어 있을 때 오류가 발생할 수도 있습니다.
이에 대한 방법은 다음과 같습니다

1. orElseThrow(Supplier)
이 메소드는 공급자 인터페이스 유형의 인수를 승인하고 이 공급자 함수에 의해 반환되는 예외를 발생시킵니다.

orElseThrow()는 Optional이 비어 있는 경우에만 예외를 발생시킵니다. 

// create empty optional
Optional<String> o = Optonal.ofNullable(null);
// throw exception when optional does not have any value
o.orElseThrow(() -> new IllegalArgumentException("Empty Optional"));
 

결과

Exception in thread “main” java.lang.IllegalArgumentException: Empty Optional
at com.codippa.demo.DemoApplication.lambda$0(DemoApplication.java:22)
at java.base/java.util.Optional.orElseThrow(Optional.java:408)
at com.codippa.demo.DemoApplication.main(DemoApplication.java:22)

2. orElseThrow()
이 메소드는 위에서 설명한 orElseThrow()와 유사하지만 어떤 인수도 허용하지 않으며 Optional이 비어 있는 경우 기본적으로 "값이 없습니다"라는 오류 메시지와 함께 java.util.NoSuchElementException을 발생시킵니다.

// create empty optional
Optional<String> o = Optonal.ofNullable(null);
// throw exception when optional does not have any value
o.orElseThrow();
 

결과

Exception in thread “main” java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.orElseThrow(Optional.java:382)
at com.codippa.demo.DemoApplication.main(DemoApplication.java:22)

이 메소드는 Java 10에 추가되었습니다.
Filtering data
값이 특정 기준을 충족하는 경우에만 결과를 반환하려는 조건이 있을 수 있습니다.

Optional은 일치할 조건을 나타내는 Predicate를 허용하고 값이 조건자(또는 조건)와 일치하면 Optional 개체를 반환하고 그렇지 않으면 빈 Optional을 반환하는 필터 메서드를 제공합니다.
Example,

Laptop apple = new Laptop("Apple");
Optional<Laptop> laptop = Optional.of(apple);
// check if
Optional<Laptop> o = laptop.filter((l) -> "Apple".equals(l.getBrand()));
System.out.println(o.orElseThrow());

 

위의 예에서는 노트북 브랜드를 확인하고 브랜드가 Apple이 아닌 경우 빈 Optional을 반환하도록 필터링합니다.

제공된 Predicate가 false를 반환하는 경우, 즉 조건이 일치하지 않는 경우 filter() 메서드는 빈 Optional을 반환합니다.
orElseThrow는 반환된 Optional이 비어 있으면 예외를 발생시킵니다.


Transforming Optional
map 및 flatMap 메소드를 사용하여 일부 유형의 Optional을 다른 유형의 Optional로 변환하는 것이 가능합니다.

 

map()
map()은 인수를 승인하고 값을 반환하는 단일 메소드가 있는 기능적 인터페이스인 java.util.function.Function 유형의 인수를 승인합니다.

map() 인수 함수는 매퍼 함수라고도 합니다.

아래 map() 메소드 예는 Optional<Laptop>을 Optional<String>으로 변환합니다.

 

Laptop apple = new Laptop("Apple");
// create Optional object
Optional<Laptop> laptop = Optional.of(apple);
// map laptop Optional to a string Optional
Optional<String> strOptional = laptop.map(l -> l.getBrand());
// get Optional value
String brand = strOptional.get();
System.out.println(brand); //prints Apple

다음은 위 예의 지도 방법과 관련된 몇 가지 중요한 사항입니다.

map(l -> l.getBrand()) 문에서 map에는 단일 인수가 포함된 Lambda 표현식이 제공되며 이는 값을 반환합니다.
map()은 map()에 인수로 제공된 Lambda 표현식의 반환 유형으로 수정된 유형의 Optional을 반환합니다.
따라서 위의 예에서 map()은 String 유형의 Optional을 반환합니다.

map()은 Optional을 반환하므로 map() 뒤에 다른 Optional 메서드를 연결하는 것이 가능합니다.

 
String brand = laptop.map(l -> l.getBrand()).map(s -> s.toUpperCase()).get();
System.out.println(brand); //prints APPLE

 

두 번째 map() 메소드에 대한 값 유형은 첫 번째 map() 메소드에서 반환된 Optional 유형인 String입니다.
인수 함수가 null을 반환하면 map()은 빈 Optional을 반환합니다.
flatMap()
Optional 객체를 변환하는 또 다른 방법은 flatMap() 메서드를 사용하는 것입니다.

map() 메소드와 flatMap() 메소드에는 두 가지 차이점이 있습니다.
1. flatMap()의 인수는 반환 유형이 Optional 객체인 함수를 기대하는 반면, map() 메소드의 인수는 위 예에서 반환 유형이 문자열과 같은 값인 함수를 기대합니다.

2. map()은 일부 유형의 값을 래핑하는 Optional을 반환하지만 flatMap()은 값을 직접 반환하며 Optional을 반환하지 않습니다.

3. flatMap()은 Optional 인수가 비어 있으면 Optional을 반환하고 Optional 인수가 비어 있지 않으면 값을 반환하며, map()은 두 경우 모두 Optional을 반환합니다.
flatMap()의 예는 다음과 같습니다.

 

Optional<Laptop> laptop = Optional.of(apple);
String result = laptop.flatMap(l -> l.getBrandOptional()).orElseThrow();
System.out.println(result); // prints Apple
이 예에서 getBrandOptional() 메소드는 브랜드 값을 래핑하는 Optional<String>을 반환하므로 flatMap()은 브랜드 값을 직접 반환합니다.

How Optional avoids NullPointerException
이 기사의 시작 부분에서 NullPointerException을 나타내는 다음과 같은 문제가 논의되었습니다.

String country = student.getStudentDetails().getAddress().getCountry().toLowerCase();

이를 방지하려면 여러 개의 Null 검사가 필요했거나 솔루션이 Optional을 사용하고 있었습니다.

이제 Optional에 대해 배운 후에는 Optional이 NullPointerException을 어떻게 방지하는지 생각해야 합니다.

이 예제에 사용된 모든 클래스를 아래와 같이 수정합니다.

 

 

class Student{

StudentDetails studentDetails;

public Optional<StudentDetails> getStudentDetails() {
return Optional.ofNullable(studentDetails);
}

}

class StudentDetails {
Address address;

public Optional<Address> getAddress(){
return Optional.ofNullable(address);
}
}

class Address {
private Country country;

public Optional<Country> getCountry(){
return Optional.ofNullable(country);
}
}

class Country {
private String country;

public Optional<String> getCountry(){
return Optional.ofNullable(country);
}
}

 

 

보시다시피, getter 메소드는 이제 객체 대신 필요한 객체를 직접 래핑하는 Optional을 반환합니다.
국가를 얻으려면 명령문이 다음과 같이 작성됩니다.

Student st = null;
String country = Optional.ofNullable(st).
flatMap(s -> s.getStudentDetails()).
flatMap(sd -> sd.getAddress()).
flatMap(a -> a.getCountry()).
flatMap(c -> c.getCountry()).
orElse("Country not found");

flatMap()에 대한 각 호출은 Optional 객체를 반환하고 마지막으로 getCountry()에서 반환된 Optional이 비어 있으면 오류 메시지가 반환되지만 NullPointerException은 반환되지 않습니다.

 

 

반응형
반응형

CompletableFuture 하나만 있으면 비동기 처리 및 여러 병렬처리를 원하는 대로 할 수 가 있다.

 

여기서 코드 구현하려는 내용은

list 데이터가 있고, 각 list 의 item 이 http 커넥션을 통해 데이터를 가져오는 등 순차적으로 수행하기에는 시간소요가 오래걸랄때 쓰레드를 이용해 시간을 줄여보고자 한다.

 

List<Item> list = itemRepository.findAll();


ExecutorService executorService = Executors.newFixedThreadPool(10);
log.info(list.size() + "개");

List<CompletableFuture<Boolean>> resultList =  list.stream().map(
    item -> CompletableFuture.supplyAsync(() -> {
    	try {
    		return this.getDataByConnection(item.getParam());
    	} catch (IOException e) {
    		e.printStackTrace();
    	}
    	return false;
    }, executorService)
).collect(Collectors.toList());

resultList.stream().map(CompletableFuture::join).collect(Collectors.toList()); 
// join 을 해야 CompletableFuture 가 stream api 내부에서 수행됨. 



private boolean getDataByConnection(String param) throws IOException {
        // 해당 내용은 okhttp 라이브러리로 다른 http 커넥션 사용해도 됨.
        Request request = new Request.Builder()
                .url("https://~~~~~~~")
                .method("GET", null)
                .build();
        Response response = client.newCall(request).execute();
        String jsonString = response.body().string();
        
        ~~~~~~ 성공 및 실패에 대한 응답
        
        return true;

    }

1. ExecutorService 로 쓰레드 생성

ExecutorService 로 생성된 쓰레드 개수만큼만 병렬쓰레드로 실행 할 수 있다.

list 의 개수에 따라 더 늘려줄 수 도 있고, list 개수가 너무 많으면 성능 부하가 생길 수 있으므로 적정수의 쓰레드를 생성하자.

 

2. list 를 stream api 를 사용하여 stream 내부에서 CompletableFuture 를 실행하자.

stream api 내부에서 map api 로  supplyAsync 든 runAsync 를 하자.

둘다 CompletableFuture 의 비동기 메소드 이지만, supplyAsync 는 응답값을 받고, runAsync 는 응답을 받지 않는 메소드이다.

 

3. 만든 executorService 쓰레드를 CompletableFuture 파라미터에 넣어주자. 

쓰레드 개수를 10개ㄹ로 해주면 아래와 같이 10개까지만 쓰레드가 생성되서 실행이 된다.

 

쓰레드풀을 넣지 않아도 된다. 

넣지 않으면 적정수의 쓰레드가 생성.

7개의 쓰레드까지만 생성이 되었다.

 

4. 마지막으로 join 을 실행시키자

CompletableFuture 를 받는 변수를 생성 하고, join 을 해줘야 해당 쓰레드풀이 실행이 되고 결과값이 반환이 된다.

CompletableFuture 의 <> 안에 들어가 있는 객체를 통해 새로운 list 로도 만들수 있다.

 

EX) 아래와 같이 list 의 item 마다 병렬처리를 통해 새로운 list 를 구성할 수 있다.

List<CompletableFuture<Item>> resultList =  list.stream().map(
    item -> CompletableFuture.supplyAsync(() -> {
    	try {
    		return this.getDataByConnection(item.getParam());
    	} catch (IOException e) {
    		e.printStackTrace();
    	}
    	return false;
    }, executorService)
).collect(Collectors.toList());

List<Item> finalList = resultList.stream().map(CompletableFuture::join).collect(Collectors.toList());

 

 

 

반응형
반응형

Akka 솔직히 좀 개념이 좀 생소해서 어떨때에 어떻게 사용해야하는지 분간이 안가서 학습중이다.

 

Akka 정의

 

Akka는 오픈 소스 툴킷으로, JVM 상의 동시성과 분산 애플리케이션을 단순화하는 런타임이다. 

 

pom.xml


<dependency>
  <groupId>com.typesafe.akka</groupId>
  <artifactId>akka-actor-typed_2.13</artifactId>
  <version>2.6.14</version>
</dependency>
<dependency>
  <groupId>com.typesafe.akka</groupId>
  <artifactId>akka-actor-testkit-typed_2.13</artifactId>
  <version>2.6.14</version>
  <scope>test</scope>
</dependency>

 

AKKA 프로젝트 템플릿 다운로드

http://dev.lightbend.com/start/?group=akka&project=akka-quickstart-java   

 

AKKA 는 자바개발환경에서는 maven, gradle 로 실행 가능하고, 스칼라 사용시 SBT 로 빌드 후 사용 가능하다.

프로젝트를 받게되면

AkkaQuickstart 라는 java 파일이 있다.

//#actor-system
final ActorSystem<GreeterMain.SayHello> greeterMain = ActorSystem.create(GreeterMain.create(), "helloakka");
//#actor-system

//#main-send-messages
greeterMain.tell(new GreeterMain.SayHello("Charles"));
//#main-send-messages

ActorSystem 을 생성하고 

main 액터에 메시지를 보낸다.

 

GreeterMain

public class GreeterMain extends AbstractBehavior<GreeterMain.SayHello> {

    public static class SayHello {
        public final String name;
        public SayHello(String name) {
            this.name = name;
        }
    }

    private final ActorRef<Greeter.Greet> greeter;

    public static Behavior<SayHello> create() {
        return Behaviors.setup(GreeterMain::new);
    }

    private GreeterMain(ActorContext<SayHello> context) {
        super(context);
        //#create-actors
        greeter = context.spawn(Greeter.create(), "greeter");
        //#create-actors
    }

    @Override
    public Receive<SayHello> createReceive() {
        return newReceiveBuilder().onMessage(SayHello.class, this::onSayHello).build();
    }


	// 3개의 GreetoerBot 을 생성 후 Greeter 에게 메시지와 누구에게 메시지를 보낼지 대상을 전달한다.
    private Behavior<SayHello> onSayHello(SayHello command) {
        //#create-actors
        ActorRef<Greeter.Greeted> replyTo =
                getContext().spawn(GreeterBot.create(3), command.name);
        greeter.tell(new Greeter.Greet(command.name, replyTo));
        //#create-actors
        return this;
    }
}

AbstractBehavior 클래스를 상속받은 GreeterMain 에게 Charles 라는 메시지를 보내면 

AbstractBehavior 클래스의 createReceive 메소드에서 receive 받기로 한 메소드가 실행이 된다.

 

createReceive 에는 메시지 받는 타입과 메시지 받은 이후의 처리를 담당하는 handler 를 설정할 수 있다.

예제에서는 handler 가 onSayHello 메소드 이다.

 

onSayHello 의 메소드는 응답받을 3개의 GreeterBot 을 만든다.

그 후

ActorRef<Greeter.Greet> greeter

ActorRef 변수 greeter 에게 메시지와 메시지를 보낼 대상일 GreeterBot(ActorRef) 객체를 보낸다.

Greeter.class

public class Greeter extends AbstractBehavior<Greeter.Greet> {

  public static final class Greet {
    public final String whom;
    public final ActorRef<Greeted> replyTo;

    public Greet(String whom, ActorRef<Greeted> replyTo) {
      this.whom = whom;
      this.replyTo = replyTo;
    }
  }

  public static final class Greeted {
    public final String whom;
    public final ActorRef<Greet> from;

    public Greeted(String whom, ActorRef<Greet> from) {
      this.whom = whom;
      this.from = from;
    }

  public static Behavior<Greet> create() {
    return Behaviors.setup(Greeter::new);
  }

  private Greeter(ActorContext<Greet> context) {
    super(context);
  }

  @Override
  public Receive<Greet> createReceive() {
    return newReceiveBuilder().onMessage(Greet.class, this::onGreet).build();
  }
  
  // GreeterMain actorRef 가 tell 할 경우 실행된다.
  private Behavior<Greet> onGreet(Greet command) {
    getContext().getLog().info("Hello {}!", command.whom);
    //#greeter-send-message
    command.replyTo.tell(new Greeted(command.whom, getContext().getSelf()));
    //#greeter-send-message
    return this;
  }
}

 

여기서 또 AbstractBehavior 클래스를 상속받은 Greeter 가

createReceive 에서 연결된 Greet 타입으로 onGreet 메소드를 실행한다.

 

이번 onGreet 메소드에는 특이하게 GreeterMain 의 onSayHello 에서 보낸 GreeterBot(ActorRef)  가 있어서 

GreeterBot 이 tell 하도록 한다.

 

GreeterBot.class

public class GreeterBot extends AbstractBehavior<Greeter.Greeted> {

    public static Behavior<Greeter.Greeted> create(int max) {
        return Behaviors.setup(context -> new GreeterBot(context, max));
    }

    private final int max;
    private int greetingCounter;

    private GreeterBot(ActorContext<Greeter.Greeted> context, int max) {
        super(context);
        this.max = max;
    }

    @Override
    public Receive<Greeter.Greeted> createReceive() {
        return newReceiveBuilder().onMessage(Greeter.Greeted.class, this::onGreeted).build();
    }
	
    // Greeter actorRef 가 onGreet 할때 tell 시킨다.
    private Behavior<Greeter.Greeted> onGreeted(Greeter.Greeted message) {
        greetingCounter++;
        getContext().getLog().info("Greeting {} for {}", greetingCounter, message.whom);
        if (greetingCounter == max) {
            return Behaviors.stopped();
        } else {
            message.from.tell(new Greeter.Greet(message.whom, getContext().getSelf()));
            return this;
        }
    }
}

 

코드 요약 

GreeterMain 에게 tell 을 한 후 Main 에 생성 되어 있는 ActorRef(greeter) 에게 tell 을 하고, 응답을 ActoerRef(greeterBot) 이 응답한다.

 

실행   

 

akka 자바 공식문서

https://doc.akka.io/docs/akka/2.5/testing.html

반응형

+ Recent posts