티스토리 뷰

728x90

스트림이란 무엇인가?

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

일단 스트림이 데이터 컬렉션 반복을 멋지게 처리하는 기능이라고 생각하자. 스트림을 병렬로 처리할 수 도 있는데 이건 내가 잘 이해를 못하고 일하는 환경에서의 필요성을 아직은 잘 못느껴서 나중에 정리

기존 코드와 자바8 스트림을 활용한 코드를 비교

// JAVA8 이전
public static List<String> getLowCaloricDishesNamesInJava7(List<Dish> dishes) {
    List<Dish> lowCaloricDishes = new ArrayList<>();    //가비지 변수 즉, 컨테이너 역할만 하는 중간변수이다. 오직 정렬 연산을 위해 필요한 변수
    for (Dish d : dishes) {
        if (d.getCalories() < 400) {
            lowCaloricDishes.add(d);
        }
    }
    List<String> lowCaloricDishesName = new ArrayList<>();
    Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
        @Override
        public int compare(Dish d1, Dish d2) {
            return Integer.compare(d1.getCalories(), d2.getCalories());
        }
    });
    for (Dish d : lowCaloricDishes) {
        lowCaloricDishesName.add(d.getName());
    }
    return lowCaloricDishesName;
}

// JAVA8
public static List<String> getLowCaloricDishesNamesInJava8(List<Dish> dishes) {
    return dishes.stream()
            .filter(d -> d.getCalories() < 400) //400칼로리 이하 요리선택
            .sorted(comparing(Dish::getCalories))   //칼로리로 정렬
            .map(Dish::getName) //이름 추출
            .collect(toList()); //모든 요리명을 리스트에 저장
}

자바8 이후 코드에서는 filter, sorted, map, collect 같은 여러 빌딩 블록 연산을 연결해서 복잡한 데이터 처리 파이프라인을 만들 수 있다.

여러 연산을 파이프라인으로 연결해도 여전히 가독성과 명확성이 유지된다.

스트림 API 특징

  • 선언형: 더 간결하고 가독성이 좋아진다.
  • 조립할 수 있음: 유연성이 좋아진다.
  • 병렬화: 성능이 좋아진다.

스트림이란?

스트림이란 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소로 정의할 수 있다.

  • 연속된 요소
    • 컬렉션과 마찬가찬가지로 스트림은 특정 요소 형식으로 이루어진 연속된 값 집합의 인터페이스를 제공
    • 컬렉션의 주제는 데이터고 스트림의 주제는 계산이다.
  • 소스
    • 스트림은 컬렉션, 배열, I/O 자원 등의 데이터 제공 소스로부터 데이털르 소비한다.
    • 정렬된 컬렉션으로 스트림을 생성하면 정렬이 그대로 유지된다.
  • 데이터 처리 연산
    • 스트림은 함수형 프로그래밍 언어에서 일반적으로 지원하는 연산과 데이터베이스와 비슷한 연산을 지원하며, 순차적으로 또는 병렬로 실행할 수 있다.

스트림의 두 가지 중요 특징

  • 파이프라이닝
    • 스트림 연산은 스트림 연산끼리 연결해서 커다란 파이프라인을 구성할 수 있도록 스트림 자신을 반환한다. 그 덕에 게으름(lazyness), 쇼트서킷(short-circuiting) 같은 최적화도 얻을 수 있다.
  • 내부 반복
    • 반복자를 이용해서 명시적으로 반복하는 컬렉션과 달리 스트림은 내부 반복을 지원한다.

스트림은 딱 한 번만 탐색할 수 있다.

public class StreamVsCollection {

    public static void main(String... args) {
        List<String> names = Arrays.asList("Java8", "Lambdas", "In", "Action");
        Stream<String> s = names.stream();
        s.forEach(System.out::println);
        // 스트림은 한 번 만 소비할 수 있으므로 아래 행의 주석을 제거하면 IllegalStateException이 발생
        //s.forEach(System.out::println);
    }
}

외부 반복과 내부 반복

컬렉션 인터페이스를 사용하려면 사용자가 직접 요소를 반복해야한다. ex) for-each 이를 외부 반복이라함.

스트림은 내부 반북오르 사용한다. 함수에 어떤 작업을 수행할지만 지정하면 모든 것이 알아서 처리된다.

//외부반복
List<String> names = new ArrayList<>();
for (Dish dish : menu) { //메뉴 리스트를 명시적으로 순차 반복
    if (d.getCalories() < 400) {
        names.add(dish.getName()); //이름을 추출해서 리스트에 추가
    }
}

//내부반복
List<String> names = menu.stream()
                                                  .map(Dish::getName)
                                                  .collect(toList());

내부 반복을 이용하면 작업을 투명하게 병렬로 처리하거나 더 최적화된 다양한 순서로 처리할 수 있다.

외부 반복을 이용하는 경우에는 병렬성을 스스로 관리해야 하는 단점이 있다. 스트림 라이브러리의 내부 반복은 데이터 표현과 하드웨어를 활용한 병렬성 구현을 자동으로 선택한다.

스트림 연산

java.util.stream.Stream 인터페이스는 많은 연산을 정의하는데 크게 두 가지로 구분할 수 있다.

dishes.stream()
      .filter(d -> d.getCalories() < 300) //중간연산
      .map(Dish::getName) // 중간연산
            .limit(3) //중간연산 
      .collect(toList()); //최종연산

연결할 수 있는 스트림 연산을 중간 연산(intermediate operation) 이라고 하고 스트림을 닫는 연산을 최종 연산(terminal operation)이라고 한다.

중간 연산

filter 나 sorted 같은 중간 연산은 다른 스트림을 반환하기때문에 여러 중간 연산을 연결해서 사용할 수 있다.

중간 연산의 특징은 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 실행하지 않는다. 즉 lazy evalution을 한다.

Stream<Integer> integerStream = Stream.of(3, -2, 5, 8, -3, 10)
        .filter(x -> x > 0)
        .peek(x -> System.out.println("Peeking " + x))
        .filter(x -> x % 2 == 0);
System.out.println("Before Collect");
List<Integer> integers = integerStream.collect(Collectors.toList());
System.out.println("After collect: "+ integers);
//스트림은 종결처리가 일어날때까지 미루고 미룬다. lazy evaluation

/**
Before Collect
Peeking 3
Peeking 5
Peeking 8
Peeking 10
After collect: [8, 10]
**/

최종 연산

최종 연산은 스트림 파이프라인에서 결과를 도출한다. 보통 최종 연산에 의해 List, Integer, void 등 스트림 이외의 결과가 반환된다.

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
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 31
글 보관함