[리액티브 프로그래밍 에피소드] RxJava의 groupBy 연산자 추가 설명

[리액티브 프로그래밍 에피소드] RxJava의 groupBy 연산자 추가 설명

안녕하세요? RxJava 강의를 진행하고 있는 Kevin입니다.

1부 강의를 수강하시던 수강생분의 질문 및 의견 사항이 있어서 다른 수강생분들에게 알리고자 블로그에 글을 남기게 되었습니다.

'Kevin의 알기 쉬운 RxJava 1부' 강의의 섹션 4 > 변환 연산자(2) 수업에서 groupBy를 설명하는 예제 코드가 있는데요. 그 중에서 두번째 예제 코드에 사용된 filter 연산자는 groupBy 연산자를 사용하면서 조금 쉬운 예를 보여드리려고 사용을 했지만 사실은 불필요하게 사용된  부분이 있습니다.

또한 groupBy 연산자의 진짜 사용 목적을 이해하기에 기존의 예제코드는 조금 부족한면이 있어서 groupBy 연산자 사용에 좀 더 적절한 예제 코드를 추가로 올려두었습니다. github에 올려둔 소스 코드를 잠깐 설명 드리자면,

==== ObservableGroupByExample03 ===

/**
* 제조사를 그룹으로 묶어서 자동차 명을 출력하는 예제
*/
public class ObservableGroupByExample02 {
public static void main(String[] args) {
Observable<GroupedObservable<CarMaker, Car>> observable =
Observable.fromIterable(SampleData.carList)
.groupBy(Car::getCarMaker);

observable
.flatMapSingle(carGroup ->
carGroup.flatMap(car ->
Observable.just(car.getCarName()))
.toList()
)
.subscribe(System.out::println);

}
}

: 제조사별로 자동차 명만 출력하는 예제입니다. groupBy( ) 연산자는 Observable<GrouppedObserveble>을 반환을 하는데 이 GrouppedObservable을 flatMapSingle( ) 연산자를 이용해서 해당 그룹별로 통지되는 Car 객체 중에 CarName만 얻은 후,(CarName을 얻기 위해서 내부적으로 flatMap() 연산자를 한번 더 사용했음)  toList()를 통해 Single로 CarName이 담긴 list를 통지하는데요. 

여기서 flatMapSingle( ) 연산자는 Observable<GroupedObservable> 이렇게 Observable이 이중으로 감싸져 있는 부분에서 바깥쪽 Observable을 한꺼풀 벗겨내고 GroupedObservable로 평탄화 시켜주는 역할을 합니다. flat이란 의미 자체가 평평하게 해준다는 그런의미가 있으니 이 의미를 기억하시면 될 것 같습니다.

flatMap( )과 달리 flatMapSingle( ) 연산자는 새로운 Observable로 데이터를 여러번 emit 하는게 아니라 연산자 이름에서와 같이 Single Observable로 단 한번만 emit을 한다는 사실 역시 기억을 해두시구요. 

flatMapSingle( ) 연산자 내부에 있는 flatMap( ) 연산자는 Observable<Car>에서 바깥쪽에 감싸져 있는 Observable을 벗겨내고 Car 객체로 평탄화 시켜주며, 이를 통해서 Car Name을 얻을 수 있고, toList( ) 연산자를 사용해서 Single로 한번만 emit 되게 됩니다.

출력 결과는 아래와 같습니다.

=====================================

[티볼리, G4렉스턴]

[SM6, SM5]

[말리부, 트래버스, 트랙스]

[쏘렌토, 팰리세이드]

그리고 네번째 예제 코드를 하나 더 추가했습니다.

==== ObservableGroupByExample04 ====

/**
* 제조사 별로 그룹으로 묶은 후, 제조사 별 차량 가격의 합계를 구하는 예제
*/
public class ObservableGroupByExample03 {
public static void main(String[] args) {
Observable<GroupedObservable<CarMaker, Car>> observable =
Observable.fromIterable(SampleData.carList)
.groupBy(car -> car.getCarMaker());

observable
.flatMapSingle(carGroup ->
Single.just(carGroup.getKey())
.zipWith(
carGroup.flatMap(car ->
Observable.just(car.getCarPrice()))
.reduce((p1, p2)-> p1 + p2)
.toSingle()
, (key, sum) -> key + ": " + sum
)
)
.subscribe(System.out::println);
}
}

: 제조사 별 차량 가격의 합계를 구하는 예제인데요.  위의 ObservableGroupByExample03  코드와 거의 같은데 다른점은 제조사 별 차량 가격의 합계를 구하기 위해 reduce( ) 연산자를 한번 더 사용했다는 건데요.

조금 복잡해 보이지만 flatMapSingle( ) 내부를 잠깐 설명 드리자면, 차량 가격 합계 앞에 제조사를 표시해주기 위해서 zipWith( ) 연산자를 추가적으로 사용을했는데 .zipWith( ) 연산자는 zipWith( )연산자를 호출한 Observable source와 zipWith( ) 의 파라미터로 입력된 Observable source를 결합시켜서 가공된 데이터를 반환하는 연산자인데요.

위 코드에서 보시면, flatMapSingle( ) 내부의 Single.just(carGroup.getKey())의 Single과 zipWith( ) 의 파라미터로 입력된 Single(마지막에 toSingle( )이 붙어서 Single로 변환 됨)을 결합해서 새로운 데이터, 여기서는 key + ": " + sum 을 반환합니다.

reduce( ) 연산자는 파라미터로 입력된 데이터 두개씩 누적하여 더해서 최종 결과로 하나의 데이터만 반환하기 때문에 Observable.just(1개 데이터)로 호출할 수 있고, 이를 다시 toSingle( ) 연산자를 사용해서 Single 로 변환될 수 있는것입니다.

출력 결과는 아래와 같습니다.

================================

CHEVROLET: 91000000

HYUNDAE: 61000000

SSANGYOUNG: 66000000

SAMSUNG: 75000000

좋은 지적을 해주신 윤지상님께 감사드리고, 다른 의견 있으시면 언제든지 문의 주세요.

감사합니다.

댓글을 작성해보세요.

채널톡 아이콘