본문 바로가기
부트캠프

자바 기초 스터디 4주차 - Stream 사용하기

by 데브겸 2023. 11. 16.

Stream이란?

Java8에서부터 등장한 Stream은 컬렉션 혹은 배열의 요소들에 대한 반복 처리를 효율적으로 처리할 수 있게 하는 기능이다. 과거에는 for나 for each 등을 통해 순차적으로 요소에 접근하며 작업하였기 때문에 코드가 다소 복잡해지거나, 성능이 좋지 못했다. 하지만 Stream이 등장하면서 이를 선언형으로 보다 가독성이 좋으면서도 작성하기도 쉬운 형태로 이를 처리할 수 있게 되었다.

 

Stream의 특징

1. 선언적인 코드 작성 방식
여러 함수형 인터페이스들을 이용하면서 ‘무엇을(what)’ 하는지를 선언하고, 그 내부적인 동작에 대해서는 숨기는 방식으로 코드를 적는다. 아래 코드에서 for문은 어떻게 데이터를 처리할 것인지에 대해서 내부 동작을 하나하나 설정해주었다면, stream을 사용한 코드에서는 각 요소를 두 배로 만들어라는 의도 선언만 할뿐, 내부적인 반복이나 조건 체크 등에서는 얘기하지 않는다. (즉, 반복문이 코드로 보이지 않는다는 것이고 이는 곧 간결하고 가독성 좋은 코드로 반복을 처리할 수 있다는 의미이기도 하다. 처리해야 하는 연산이 복잡해질 수록 더 큰 장점이 된다.)

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> doubled = new ArrayList<>();

for (int number : numbers) {
    doubled.add(number * 2);
}

List<Integer> doubled = numbers.stream()
                               .map(number -> number * 2)
                               .collect(Collectors.toList());

 

 

2. Stream은 일회용이며 원본 데이터를 바꾸지 않는다.

stream은 stream을 만드는 원본 데이터를 건드리지 않고 복사하여 사용한다. 즉 원본 데이터의 보존을 보장하면서도 그 데이터를 연산하여 새로운 데이터를 만들어 다른 곳에서 사용할 수 있다. 하지만 이때 stream은 한 번 사용하여 닫히면 재사용이 불가하기 때문에, stream을 통해 만들어진 데이터를 재사용하고 싶다면 별도의 자료형에 담아 사용해야 한다.

 

 

Stream의 사용방법

Stream은 아래 3단계를 따라 사용할 수 있다. 각 단계들을 도트(dot)을 통해 이어 작성하는 방식으로 진행된다.

 

1. Stream 생성: Stream을 생성하고자 하는 변수에 대해서 stream을 생성한다. 이때 stream 생성 가능한 것들은 연속된 데이터를 가지고 있는 것들이 보통이며(컬렉션, 배열, 파일 등) 특이하게 빈 스트림을 생성할 수도 있다.

 

2. 중간 연산: 중간 연산은 stream을 받아 stream을 반환하는 연산이다(따라서 중개 연산을 연속으로 몇 번이고 사용할 수도 있다).

  1. Stream 필터링 : filter(), distinct()
  2. Stream 변환 : map(), flatMap()
  3. Stream 제한 : limit(), skip()
  4. Stream 정렬 ; sorted()
  5. Stream 연산 결과 확인 : peek()

 

3. 최종 연산: 중간 연산을 통해 만들어진 stream을 받아 stream이 아닌 값을 반환하여 최종적으로 마무리짓는 연산이다. 위에서 stream의 특성에서 얘기했듯, stream은 종료되면 재사용이 불가하기 때문에 최종 연산 뒤에는 모든 stream은 종료가 되고 없어지게 된다.

  1. 출력 : forEach()
  2. 소모 : reduce()
  3. 검색 : findFirst(), findAny()
  4. 매칭 : anyMatch(), allMatch(), noneMatch()
  5. 값 계산(통계적 연산) : count(), min(), max()
  6. 값 계산 : sum(), average()
  7. 수집 : collect()

 

예시

아래는 과일 이름들을 가지고 있는 List에 대해서 stream 연산을 수행하는 예시다. 각각 필터링, 반복, 집계하는 연산이며, 코드에서 볼 수 있든 반복문을 사용하지 않고도 직관적인 코드로 반복 작업을 처리할 수 있음을 확인할 수 있다.

public class StreamExample {
    public static void main(String[] args) {
        List<String> items = Arrays.asList("apple", "banana", "pear", "kiwi");

        // 필터링과 매핑
        List<String> filtered = items.stream()
                                     .filter(s -> s.startsWith("a"))
                                     .map(String::toUpperCase)
                                     .collect(Collectors.toList());
        System.out.println(filtered); // [APPLE]

        // 반복
        items.stream()
             .forEach(System.out::println);

        // 집계
        long count = items.stream()
                          .filter(s -> s.contains("a"))
                          .count();
        System.out.println("Count: " + count); // 3
    }
}