ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 람다와 스트림 - 중간 연산 part 2
    백엔드/자바 2023. 2. 13. 05:47
     

    람다와 스트림 - 중간 연산 part 1

    람다와 스트림 - 스트림의 정의와 특징 스트림의 정의와 그 필요성 한마디로 말하면 컬렉션이나 배열을 다루기 쉽게 만든 것이다. 기존의 컬렉션 프레임웍을 보면 위와 같이 List, Set, Map의 인터

    sgcomputer.tistory.com

     

    이전 글에서 우리는 스트림의 중간 연산에 대해 공부해봤다.

     

    간단히 복습해보자면 스트림 중간 연산은 컬렉션 데이터를 쉽게 재가공하기 위해 존재한다.

     

    그렇기에 연산 반복이 가능하고 반환 데이터의 타입이 Stream이다.

     

    오늘은 지난 번에 이어 남은 중간 연산들에 대해 공부해보고자 한다.

     

    중간 연산 - Map(): 스트림의 요소 변경

     

    이전에 공부한 중간 연산 메서드 sorted(), skip() 등은 컬렉션의 데이터들을 거르거나 잘라내는 것만 가능했다.

     

    하지만 map()의 경우는 컬렉션이 가진 데이터 즉 스트림의 요소들을 수정하는 것이 가능하다.

     

    위 코드와 같이 map을 이용하면 데이터를 사용자 편의에 맞게 가공이 가능하다.

     

     

     

    당연히 중간 연산이므로 위와 같이 중복 사용이 가능하다.

     

    map()을 활용하면 단순 연산 뿐 아니라 더욱 복잡한 데이터 가공이 가능하다.

     

    중간 연산 - peek(): 중간 연산 조회

     

    peek( )은 간단히 말하면 forEach()처럼 메서드의 연산 결과를 조회할 때 사용한다.

     

    forEach()와 차이점이라면 stream이 소모되지 않기 때문에 연산과 연산 사이에 넣어 결과를 확인하기에 좋다.

     

    사실 중간 연산을 다중 반복하다보면 오류가 나는 경우가 있다.

     

    그럴 때마다 peek()을 중간에 끼워넣어 디버깅을 할 수 있다.

     

     

     

    위 그림을 보면 알겠지만 peek( )도 다른 중간 연산과 마찬가지로 Stream타입으로 데이터를 반환하고 있다.

     

     

     

    그 결과 Stream이 유지된 상태로 중간 결과 조회가 가능하다.

     

    중간연산 - flatMap( ): 평탄화 및 연산

    스트림 작업을 하다보면 원하는 결과를 얻지 못하는 경우가 종종있다.

     

    이유야 여러가지가 있겠지만 보통은 스트림 혹은 데이터들의 이중 구조로 인해 오류가 나는 경우가 많다.

     

    예를 들어보며 알아보자.

     

    (1) flatMap()없이 데이터 다뤄보기

    그림 1

    위 코드와 같이 두개의 문자열을 요소로 갖는 리스트가 있다고 가정해보자.

     

    아마 그림으로 나타내면 그림 1과 같을 것이다.

     

    그림 2

    우리가 최종적으로 만들어야 하는 건 문자들을 단어별로 분해 후 중복을 제거한 새로운 리스트이다.

     

    아마도 완성되면 그림 2와 같은 모습의 리스트가 완성될 것이다.

     

    이제 스트림을 생성해서 위에서 이야기한대로 데이터를 가공해보자.

     

     

     

    지금까지 배운 내용을 토대로 스트림을 활용해 데이터를 가공하면 위와 같은 코드가 될 것이다.

     

    스트림을 만들고 map을 통해 문장을 자르고 중복 제거 후 리스트를 반환 후 출력한 코드다.

     

    이제 코드에 따른 출력 내용을 확인해보자.

     

     

     

    보시다시피 예상과는 전혀 다른 결과가 출력됐다.

     

    이건 왜 그런걸까?

     

    (2) 연산 과정에서 스트림의 변화

    이전 단락에서 지금까지 배운 내용을 토대로 리스트를 변경하는 방법을 배워봤다.

     

    하지만 예상과는 다르게 원하는 결과를 얻지 못했다. 왜 그랬을까?

     

    바로 연산 과정에서 스트림의 특징을 이해하지 못했기 때문이다.

     

    우선 처음으로 돌아가보자.

     

    그림 3

    우리는 맨 처음 그림 3과 같은 상태의 리스트를 만들었다.

     

    이 상태에서 리스트를 스트림으로 만든다고 가정해보자.

     

     

    그림 4

    스트림을 생성한다면 그림 4와 스트림이 생성된다.

     

    이제 스트림을 생성했으니 문자를 잘라보자.

     

     

    그림 5

    아마 문자를 이상적으로 자른다면 그림 5처럼 될 것이다.

     

     

    그림 6

    하지만 결과는 이미 위에서 실패해서 예상했겠지만 그림 5가 아닌 그림 6과 같이 된다.

     

     

     

    위와 같이 코드로 검증해봐도 그림 6과 같은 상황인 것을 확인해볼 수 있을 것이다.

     

    왜 그런걸까? 이건 현재 스트림의 구조가 원하는 연산에 맞게 제대로 동작하지 못하는 구조이기 때문이다.

     

     

    그림 7

    그림 7에서 보듯 리스트의 데이터를 기반으로 스트림을 생성하면 오른쪽과 같이 된다.

     

    이 상황에서 map의 내용을 실행하면 아래의 그림 8과 같이 진행된다.

     

     

    그림 8

    그림 8에서 보듯이 map을 실행하면 요소가 2개가 있으므로 split()은 각각의 요소 안에서 실행된다.

     

    즉 split()은 이뤄지지만 우리가 원하는 것과 달리 그것이 각자의 범위 안에서 개별적으로 이뤄진다는 것이다.

     

     

    그림 9

    만약에 우리가 원하는 대로 결과를 내려면 그림 9와 같은 상황을 만들고나서 split()이 실행되어야 할 것이다.

     

    이건 어떻게 해야하는걸까?

     

    (3) flatMap()의 사용

     

    이러한 문제를 해결하는 것이 바로 flatMap()이다.

     

    map()이 단순히 요소를 전달 받아서 연산만 하는 역할이라면,

     

    flatMap()은 평탄화(flattening)와 연산을 같이 한다.

     

    즉 flatMap()은 그림 9와 같이 스트림의 구조를 단순화하는 작업을 하게 만들어졌다는 것이다.

     

    우선 결과부터 확인하고 추가적으로 더 설명하도록 하자.

     

     

     

    flatMap()을 사용한 결과는 그림 2처럼 우리가 원하던 결과와 동일하게 나왔다.

     

    앞서 말했듯 flatMap()은 평탄화 작업을 주목적으로 하는 메서드다.

     

    평탄화 작업이란 이번 예시에서 사용된 것 이외의 이중 리스트, 이중 스트림 등의 요소를 단순화시켜준다.

     

    사용법도 아주 단순하다.

     

     

     

    flatMap()의 매개변수로 스트림을 생성해주는 Stream.of() 혹은 Stream()을 전달해주기만 하면 된다.

     

    사실 파고 들어봐도 flamMap()의 매개변수로 왜 꼭 스트림 생성 메서드를 전달해야하는지 정확한 이유는 써있지 않지만,

     

    추측하건데 모든 데이터를 평탄화 상태에서 작업 한 후 하나의 스트림으로 다시 묶어서 반환하기 위함인 것으로 보인다.

     

     

     

    조금 더 간단하게 예시로 설명해보자면 위와 같이 사용하면 된다.

     

    위 코드는 문자 배열 2개를 합친 스트림을 만들어 출력하는 예시이다.

     

    위와 같이 그저 flatMap()안에서 그저 주어진 인수를 가지고 스트림을 생성해주면 된다.

     

     

     

    당연히 결과는 위와 같이 A~F까지 모두 하나로 합쳐져 출력된 결과를 볼 수 있다.

     

    지금까지의 과정에서 드는 의문이 하나가 있을 수 있다.

     

    flatMap()은 이중 요소들의 평탄화가 가능한데, map()에서는 불가능할까?

     

    답변만 말하자면 상황에 따라 다르겠지만 map()을 통해서도 어느 정도 가능하다.

     

    하지만 코드가 길어지고 더 번거롭기 때문에 flatMap()을 쓰는 편이 유리하다.

     

     

     

    참고로 map() 안에서 flatMap()에서 하듯이 스트림을 생성하면 오히려 스트림이 하나더 덧씌워진다.

     

    위의 코드처럼 map()안에서 stream.of()로 스트림을 생성해주면 아래와 같은 결과를 볼 수 있다.

     

     

     

    스트림 안에 오히려 스트림을 하나 더 생성한 결과를 확인할 수 있다.

     

    정리하자면 스트림을 다루다가 중간에 오류가 난다면 스트림의 구조를 확인해보자.

     

    스트림은 데이터가 이중으로 쌓여있다면 제대로 동작하지 못하기 때문이다.

     

     

Designed by Tistory.