티스토리 뷰

728x90
반응형

 

목차

     

    Method vs. Operator

     

    별생각 없이 메서드나 연산자라는 단어를 마구 뒤섞어 쓰다가 의문이 들었다.

     

    같은 뜻의 단어를 두 개나 사용할 필요가 있을까? 하고.

     

    해서 연산자 정리도 할 겸 아주 얕게 알아보았는데,

     

    결론부터 말하면 둘은 다른 개념이지만 아주 남남은 아니다. 요약하면 아래와 같다.

     

    • 메서드(Method) - 클래스나 객체 내부에 정의된 함수. Mono와 Flux 역시 내부에 많은 메서드를 가지고 있음
    • 연산자(Operator) - 실제 연산에서 사용되는 메서드. 스트림의 데이터 처리를 연산이라고 부르기 때문에 붙은 명칭

    요약하면 스트림 연산의 맥락에서 바라본 메서드를 연산자라고 부르는 것이다.

     

    또한 리액티브 스트림에서 사용되는 연산자 중엔 표준사양에서 정의된 연산자와 그렇지 않은 연산자가 있는 모양이지만,

     

    여기에서는 우선 구분하지 않고 사용 빈도순으로 나열해 정리한다.

     

    map()

     

    map 연산자는 스트림 내부의 요소를 <스트림 내부 자료형의 변환 없이> 새로운 값으로 매핑한다.

     

    리턴 값은 Mono와 Flux를 모두 가질 수 있으며, 간단하게 코드로 예를 들면 아래와 같다.

    Flux.just(1, 3, 5, 7, 9)
        .map(integer -> integer * 3)
        .subscribe(System.out::println);
    3
    9
    15
    21
    27

     

    flatMap()

     

    flatMap은 들어온 데이터를 이용해 새로운 데이터스트림으로 매핑하는 데 사용된다.

     

    <스트림 내부 요소 자료형의 변환>이 일어날 수 있으며, 결과를 평면화시켜 하나의 스트림으로 반환한다.

     

    역시 Mono와 Flux를 리턴값으로 가진다.

     

    예를 들면 아래와 같다.

    Flux.just(1, 3, 5, 7, 9)
        .flatMap(integer -> Mono.just((char) integer.intValue()))
        .subscribe(System.out::println);

    주어진 입력을 아스키코드로 바꾼 뒤 출력하는 코드이다.

     

    doOnNext()

     

    doOnNext는 데이터 발행 직전에 특정 기능을 사용하고 싶을 때 사용한다.

     

    데이터가 여러 번 발행된다면 해당 횟수만큼 실행되며, 이런 특성 덕분에 로깅, 데이터 검증, 처리 등에 써먹을 수 있다.

     

    내 경우엔 처음엔 멋모르고 스트림을 가공하는 거라고 생각해 남발했으나 전혀 생각처럼 동작하지 않았던 연산자이기도 하다.

     

    그 이유는 doOnNext의 경우 데이터 스트림을 변경하지 않기 때문이며, 위에 데이터 처리라고 적었지만

     

    실제 데이터 처리나 비즈니스 로직은 map이나 flatMap을 이용해서 처리해야 한다.

     

    따라서 doOnNext의 주목적은 현시점의 데이터 검증이나 로깅으로 좁혀지는데,

     

    디버깅이 굉장히 어려운 웹플럭스의 특성상 상당히 유용하게 사용된다.

     

    역시 코드로 보면 아래와 같다.

    Flux.just(1, 5, 9)
        .doOnNext(integer -> System.out.println("Before map: " + integer))
        .map(integer -> integer * 3)
        .doOnNext(integer -> System.out.println("After map: " + integer))
        .subscribe();
    Before map: 1
    After map: 3
    Before map: 5
    After map: 15
    Before map: 9
    After map: 27

     

    filter()

     

    filter는 알고리즘과 웹 개발 중 스트림을 사용할 때 많이 썼던 연산자라서 익숙하다.

     

    스트림에서 특정 기준에 맞는 데이터만 추출하여 반환한다.

    Flux.just(1, 3, 5, 7, 9)
         .filter(integer -> integer >= 5)
         .subscribe(System.out::println);
    5
    7
    9

     

    reduce()

     

    reduce 연산자 역시 스트림을 사용할 때 많이 써먹던 것이다.

     

    데이터 스트림에 존재하는 요소를 하나의 값으로 축약한다.

     

    이는 모든 데이터를 합하거나 빼거나 최대, 최솟값을 구하는 연산을 포함한다.

    Flux.just(1, 3, 5, 7, 9)
        .reduce(0, Integer::sum)
        .subscribe(System.out::println);
    25

     

    zip()

     

    zip 연산자는 여러 개의 데이터 스트림을 병합해 하나의 스트림을 만드는 데 사용된다.

     

    이 연산자를 적당히 사용하면 코드의 가독성은 좋아지고 길이는 짧아지게 된다.

     

    이때 주의사항은 각 스트림에서 발행하는 요소의 개수가 같아야 한다.

     

    만약 둘의 개수가 다르다면 둘 중 요소의 개수가 적은 쪽에 맞춰서 병합이 이루어진다.

    Flux<Integer> flux1 = Flux.just(1, 3, 5, 7, 9);
    Flux<Integer> flux2 = Flux.just(2, 4, 6, 8, 10);
    
        Flux.zip(flux1, flux2)
            .subscribe(System.out::println);
    [1,2]
    [3,4]
    [5,6]
    [7,8]
    [9,10]

     

    concat()

     

    concat 역시 두 개 이상의 스트림을 병합하는 데 사용된다. 하지만 zip과는 다르게 합쳐지는 스트림의 순서를 지킨다.

     

    다른 말로 하자면 앞의 스트림에 뒤의 스트림을 이어 붙이는 역할을 한다.

     

    당연히 방출되는 데이터의 순서도 지켜진다.

    Flux<Integer> flux1 = Flux.just(1, 3, 5, 7, 9);
    Flux<Integer> flux2 = Flux.just(2, 4, 6, 8, 10);
    
        Flux.concat(flux1, flux2)
            .subscribe(System.out::println);
    1
    3
    5
    7
    9
    2
    4
    6
    8
    10

     

    switchIfEmpty()

     

    switchIfEmpty는 스트림이 비어있는 경우 특정 스트림을 지정해서 리턴하는 역할을 한다.

     

    즉, 비어있는 스트림을 지정한 스트림으로 변경하는 역할을 한다.

     

    주로 비어있는 스트림에 대해 예외처리를 할 때 사용하며, 나의 경우 디비 조회 결과가 비어있는 경우에 사용했다.

     

    아주 간단하게 예를 들면 아래와 같다.

    Mono.empty()
        .switchIfEmpty(Mono.just("Empty"))
        .subscribe(System.out::println);
    Empty

     

    onErrorResume()

     

    onErrorResume은 위와 비슷하지만, 비어있는 경우가 아닌 에러가 발생했을 때의 예외처리 역할을 한다.

     

    그 예외처리란 마찬가지로 스트림을 다른 스트림으로 대체하는 것으로 이루어지며,

     

    코드로 바로 보자면 대략 아래와 같다.

    Flux.just(1, 2, 3, 4, 5)
        .map(integer -> {
            if(integer == 3) {
                throw new RuntimeException("Error");
            }
            return integer;
        })
        .onErrorResume(throwable -> Flux.just(-1, -2, -3))
        .subscribe(System.out::println);
    1
    2
    -1
    -2
    -3

     

    collectList()

     

    collectList는 MVC 패턴에서 스트림을 사용할 때도 많이 썼던 연산자와 비슷하게 생겼다.

     

    실제로 역할도 비슷한데, 방출된 모든 데이터를 리스트에 담아서 반환한다.

     

    추가로 당연하다면 당연하게도 이 연산자는 Flux에서 사용된다.

    Flux.just(1, 2, 3, 4, 5)
        .collectList()
        .subscribe(System.out::println);
    [1, 2, 3, 4, 5]

     


    당연히 이외에도 굉장히 많은 연산자가 존재한다.

     

    그러나 그걸 하나하나 설명을 달기엔 일단 내가 너무 모르기도 하고,

     

    처음에 적은 대로 내가 사용하는 빈도순으로 대략 나열해서 정리해 보았다.

     

    실력이 더 늘면 글이 이어질지도..?

     

    어쨌거나 오늘은 끝!

    반응형
    댓글
    공지사항
    최근에 올라온 글
    최근에 달린 댓글
    Total
    Today
    Yesterday
    링크
    «   2024/05   »
    1 2 3 4
    5 6 7 8 9 10 11
    12 13 14 15 16 17 18
    19 20 21 22 23 24 25
    26 27 28 29 30 31
    글 보관함