ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 람다와 스트림 - 스트림의 정의와 특징
    백엔드/자바 2021. 9. 15. 00:24

    스트림의 정의와 그 필요성

     

    한마디로 말하면 컬렉션이나 배열을 다루기 쉽게 만든 것이다.

     

    기존의 컬렉션 프레임웍을 보면 위와 같이 List, Set, Map의 인터페이스로 구성된 것을 알 수 있다.

     

    이 중에서 List와 Set은 사용법이 비슷하지만, Map은 그 성격이 다르다.

     

    이는 즉 컬렉션을 다룰 때 List, Set과 달리 Map을 다루기 위한 방법 또한 다르단 것을 의미한다.

     

    스트림은 이러한 불편함을 해소하고자 만들어졌다.

     

    스트림은 데이터 소스를 추상화하고, 데이터를 다루는데 필요한 메서드들을 정의한 인터페이스다.

     

    예시를 한번 보도록 하자.

     

     

     

    예를 들어 위와 같이 1부터 5까지 숫자를 가진 배열과 리스트가 있다고 가정해보자.

     

    이제 이 배열과 리스트 중에서 짝수만 출력하려면 어떻게 해야할까?

     

     

     

    일반적으로는 위와 같은 방식으로 짝수를 걸러내게 될 것이다.

     

    이게 가장 간단하고 코딩 교육 초기에 배우는 방식이다.

     

    그런데 이전에 람다와 function패키지에 대해서 배우고나서 다시 보면 저 코드들이 산만해보일 것이다.

     

    그럴때 스트림을 써주면 된다.

     

     

     

    위 코드는 이전과 같은 기능을 하는 코드다.

     

    다만 스트림을 이용했기 때문에 줄줄이 늘어놓는 코드에 비해서 훨씬 간단하게 표현한 것이 특징이다.

     

    스트림은 처음 보기에는 어려워보일지 몰라도 익히기만하면 코드를 훨씬 간결하게 표현이 가능하다.

     

    스트림의 생성과 흐름

    스트림은 이전 단락에서 말했듯이 컬렉션, 배열을 쉽게 다루기 위한 인터페이스라고 말한바 있다.

     

    즉 컬렉션, 배열을 스트림으로 만들어서 미리 정의된 메서드를 이용해 데이터를 다루는 것이다.

     

    그림으로 흐름을 나타내면 아래와 같다.

     

     

     

    우선 컬렉션에 해당하는 List, Set, Map와 배열을 스트림으로 생성이 가능하다.

     

    그 다음 생성된 스트림을 이용해 중간 연산을 진행한다.

     

    마지막으로 최종 연산을 진행하면 스트림 사용이 완료된다.

     

    이때 스트림을 만드는 것은 여러 데이터 형식을 하나로 표준화한다는 의미로 

     

    스트림으로 만들고나면 스트림 인터페이스에 정의된 메서드를 사용할 수 있다.

     

    이때 기억하면 좋을 점은 중간 연산은 여러번 가능하고, 최종 연산은 단 한번만 가능하다는 것이다.

     

    식으로 직접 보면 더 이해가 편할 것이다.

     

     

     

    위 내용을 보면 알겠지만 스트림을 생성하고 체이닝으로 각종 연산을 한번에 수행하는 것을 볼 수 있다.

     

    이때 sorted(), limit(), distinct()는 중간 연산 메서드로서 그 연산 결과가 스트림이다.

     

    그래서 연산의 결과로 스트림을 서로 넘겨주며 여러 차례 중간 연산 메서드 이용이 가능하다.

     

    하지만 최종 연산 메서드에 해당되는 forEach()의 경우 연산 결과는 스트림이 아니다.

     

    추후 배우겠지만 모든 스트림은 iterator처럼 일회용인데,

     

    최종 연산은 그 결과가 스트림이 아니므로 최종 연산 메서드를 사용하면 해당 스트림은 더이상 쓸 수 없다.

     

    스트림의 특징

    1. 스트림은 데이터 소스를 변경하지 않는다.

     

     

    기존 배열이나 컬렉션 데이터를 스트림으로 변환하더라도 원본 데이터는 변경되지 않는다.

     

    위의 코드를 보면 배열 중 짝수만 따로 출력을 하더라도, 배열이 변경되지 않는 것을 확인할 수 있다.

     

     

    2. 스트림은 일회용이다.

     

     

    스트림은 일회용이다. 이전에 Iterator에 대해 배웠다면 그와 비슷한 개념이라 생각하면 된다.

     

    위의 코드처럼 배열을 스트림으로 만들어서 최종연산 메서드인 forEach()를 사용했다면

     

    그 다음 다시 스트림을 쓸 때 오류가 나는 것을 알 수 있다.

     

    이때 스트림이 사용 종료 여부는 최종 연산 메서드를 쓰느냐 안쓰느냐에 달렸다.

     

    스트림은 일회용으로서 한번 소모하고 나면 다신 사용이 불가능하므로 새로 생성해야 한다.

     

     

     

    만약 스트림은 재사용하고 싶다면 위 코드처럼 스트림을 재생성해주기만하면 된다.

     

    어렵지도 않고 그냥 연산을 한번 끝마친 스트림은 재생성해서 사용해준다고 기억해두면 된다.

     

     

    3. 스트림의 연산은 최종 연산이 수행되기 전까지 중간 연산이 수행되지 않는다.

     

    위 코드는 로또 번호를 추출하는 코드다.

     

    참고로 distinct()는 중복제거, limit()는 주어진 숫자만큼 요소 추출, sorted()는 정렬하는 중간 연산 메서드다.

     

    이때 주의해서 봐야할 것은 바로 가장 처음 선언한 난수를 생성하는 무한 스트림이다.

     

    이 무한 스트림은 1~45까지의 정수를 무한하게 생성하는 스트림으로서 무한히 정수를 생성해낸다.

     

    그런데 아래 스트림의 중간 연산을 보면 무한 생성 정수의 중복을 제거하고 정렬을 하는 것을 볼 수 있다.

     

    어떻게 무한하게 생성되는 정수를 그렇게 중복제거하고 정렬을 할 수 있을까?

     

    이는 바로 스트림의 특징 중 하나인 지연 연산때문이다.

     

    스트림은 효율성을 이유로 중간 연산이 있을 경우 매번 연산하지 않고, 최종 연산에 다다르면

     

    그제서야 이전까지 있던 중간 연산을 한 번에 진행하는 것이다.

Designed by Tistory.