스트림(Stream) 개념, 특징

스트림(Stream)이란?

스트림(Stream)의 기능이 생겨난 이유

많은 수의 데이터를 다룰 때, Collection(List, ArrayList, Set, HashSet 등)이나 Array(배열)을 활용했었다. 이 때, for문과 Iterator를 사용해서 코드를 작성할 때면 코드가 너무 길어지고 알아보기 어려웠었다. 또한 정렬을 수행하려고 할 때, 자료 구조(Collection, Array 등)에 따라서 Collections.sort(), Arrays.sort()와 같이 다른 메서드를 사용해야 하는 것이 불편했다.

이렇나 문제점들을 해결하기 위해서 만든 것이 ‘스트림(Stream)’이다. 스트림은 데이터 소스를 추상화하고, 데이터를 다루는 데 자주 사용되는 메서드들을 정의해 놓았다. 데이터 소스를 추상화하였다는 것은, 데이터 소스가 Collection이건 Array건 상관없이 같은 방식으로 다룰 수 게 되었다는 것이다. 이로 인해 Collections.sort(), Arrays.sort()와 같이 각기 다른 데이터 소스마다 ‘정렬’과 같은 기능들을 각각 구현해주지 않아도 되므로 재사용성이 높아졌다.

데이터를 정렬하고 화면에 출력하는 밑의 코드를 보고 스트림(Stream)의 장점을 느껴보자.

스트림(Stream)을 사용하지 않은 경우

String[] strArr = { "aaa", "bbb", "ccc" };
List<String> strList = Arrays.asList(strArr);

Arrays.sort(strArr);
Collections.sort(strList);

for (String str : strArr) {
    System.out.println(str);
}
for (String str : strList) {
    System.out.println(str);
}

스트림(Stream)을 사용한 경우

String[] strArr = { "aaa", "ddd", "ccc" };
List<String> strList = Arrays.asList(strArr);

Stream<String> strStream1 = strList.stream();
Stream<String> strStream2 = Arrays.stream(strArr);

strStream1.sorted()
    .forEach(System.out::println);
strStream2.sorted()
    .forEach(System.out::println);

스트림(Stream) 한 줄 요약

많은 수의 데이터를 다루는 자료구조(List, Array 등)를 가독성 있고, 편하게 다룰 수 있도록 만들어주는 기능

스트림(Stream)의 특징

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

스트림은 데이터 소스로부터 데이터를 읽기만할 뿐, 데이터 소스를 변경하지 않는다.

String[] strArr = { "aaa", "ddd", "ccc" };

System.out.println(Arrays.toString(strArr)); // 정렬 전 : [aaa, ddd, ccc]
Arrays.stream(strArr)
    .sorted();
System.out.println(Arrays.toString(strArr)); // 정렬 후 : [aaa, ddd, ccc]

즉, 위의 예시에서 데이터 소스인 strArr는 변경되지 않는다.

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

스트림은 1회용이다. 한 번 사용하면 닫혀서 다시 사용할 수 없다. 필요하다면 스트림을 다시 생성해야 한다.

String[] strArr = { "aaa", "ddd", "ccc" };
Stream<String> strStream = Arrays.stream(strArr);
strStream.sorted().forEach(System.out::println);
strStream.sorted().forEach(System.out::println); // 에러 : 스트림이 이미 닫혔음

3. 스트림은 모든 요소에 대한 반복 처리를 ‘내부 반복’으로 처리한다.

스트림을 이용한 작업이 간결할 수 있는 비결중의 하나가 바로 ‘내부 반복’이다. 내부 반복이라는 것은 반복문을 메서드의 내부에 숨길 수 있다는 것을 의미한다. for문에 대한 긴 반복문을 forEach()를 활용해서 내부로 로직을 숨겼다.

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

4. 스트림의 연산

스트림이 제공하는 다양한 연산을 이용해서 복잡한 작업들(정렬, 필터링 등)을 간단히 처리할 수 있다. 스트림이 제공하는 연산은 ‘중간 연산’과 ‘최종 연산’으로 분류할 수 있다.

  • 중간 연산 : 연산결과를 스트림으로 반환 O. 중간 연산을 연속해서 연결할 수 있다.
  • 최종 연산 : 연산결과를 스트림으로 반환 X. 스트림의 요소를 소모하므로 단 한번만 가능.

References