Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

참새의 이야기

Chapter 4. 스트림 소개 본문

JAVA/모던 자바 인 액션

Chapter 4. 스트림 소개

참새짹짹! 2023. 11. 16. 12:07
  • SQL에서 where로 처리하던 로직을 컬렉션으로도 만들 수 있지 않을까?
  • 크기가 큰 컬렉션은 병렬적으로 처리해야 할 텐데 이 코드를 단순하게 할 수 없을까?

이 두 질문의 답으로 스트림을 제안한다.

지난 프로젝트를 하면서 스트림을 안 썼다면 코드가 훨씬 길고 복잡해지겠다는 생각을 했다.

스트림을 야무지게 쓰기 위해 스트림이 무엇인지부터 알아보자.

스트림이란

스트림은 자바 8에 새로 추가된 기능으로, 선언형으로 컬렉션 데이터를 처리할 수 있다.

선언형이라는 것은 데이터를 임시 구현 코드 대신 질의로 처리할 수 있다는 의미다.

멀티 스레드 코드를 구현하지 않아도 병렬 처리를 할 수 있다는 점도 장점이다.

다음의 자바 코드를 스트림으로 개선해보자.

List<Dish> lowCaloricDishes = new ArrayList<>();
for (Dish dish:menu) {
    if (dish.getCalories() < 400) {
        lowCaloricDishes.add(dish);
    }
}

Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
    public int compare(Dish dish1, Dish dish2) {
        return Integer.compare(dish1.getCalories(), dish2.getCalories());
    }
});

List<String> lowCaloricDishesName = new ArrayList<>();
for (Dish dish : lowCaloricDishes) {
    lowCaloricDishesName.add(dish.getName());
}

위의 코드는 장황하고, 의도가 한눈에 들어오지 않고, 로직과 무관한 부분도 있다.

아래와 같이 개선한다면 filter로 걸러서 이름을 추출하고 리스트로 만든다는 파이프라인이 명확하게 드러난다.

List<String> lowCaloricDishesName = menu.stream()
                .filter(dish -> dish.getCalories() < 400)
                .map(Dish::getName)
                .collect(toList());

stream()parallelStream()으로 바꾸면, 이 코드를 멀티코어 아키텍처에서 병렬로 실행한다.

관련 내용은 이후 다시 자세하게 다루게 되므로 여기서는 스트림을 사용했을 때의 이점에 주목하자.

선언형으로 구현했기 때문에 if나 for문을 사용하지 않을 수 있었다.

요구 사항이 바뀐다면 모든 코드를 뜯어고칠 필요 없이 filter의 파라미터나 map의 파라미터만 수정하면 된다.

선언형 코드와 동작 파라미터화를 활용했기 때문에 가능한 일이다.

스트림 정의

스트림을 정확히 정의하자면 아래와 같다.

데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소

  • 파이프라이닝: 스트림 연산은 스트림 연산끼리 연결해서 파이프라인을 구성한다. 이를 위해 각각의 연산은 스트림을 반환한다.
  • 내부 반복: 반복자를 이용하는 컬렉션과 달리 스트림은 내부 반복을 지원한다.

스트림과 컬렉션의 차이

언제 계산하는가

스트림과 컬렉션의 가장 큰 차이다.

컬렉션은 모든 값을 메모리에 저장하므로 컬렉션에 추가되기 전에 모든 값이 계산되어야 한다.

이와 달리 스트림은 요청할 때만 계산하는 고정된 자료구조다.

대신 스트림에 자료를 추가/삭제할 수 없다.

한 번만 탐색 가능

스트림은 한 번 사용되면 반복 사용할 수 없다.

Stream<Dish> stream =menu.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println);

위의 코드를 실행하고 line 2에 breakpoint를 걸어보면 아래와 같이 어떤 요소들이 들어있는지 확인할 수 있다.

한 줄 step over하고 line 3로 가서 다시 확인해 보면 아래처럼 null로 바뀌어있다.

스트림은 한 번만 소비될 수 있다는 것을 꼭 기억하자.

스트림 연산

스트림의 연산은 중간 연산과 최종 연산으로 구성된다.

최종 연산은 중간 연산들로 연결된 스트림을 닫는 연산을 말한다.

중간 연산

filter나 sorted 같은 연산을 말한다.

중간 연산은 lazy 하다는 특징이 있다.

단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 일도 일어나지 않는다.

최종 연산

스트림 이외의 결과를 반환한다,

forEach, count, collect 등의 연산이 있다.

'JAVA > 모던 자바 인 액션' 카테고리의 다른 글

Chapter 5. 스트림 활용  (1) 2023.11.18
Chapter 3. 람다 표현식  (0) 2023.11.15
Chapter2. 동작 파라미터화  (2) 2023.11.14
Chapter 1. 자바의 변화  (0) 2023.11.14