관리 메뉴

IT.FARMER

Java Stream 본문

JAVA/Stream

Java Stream

아이티.파머 2022. 10. 6. 10:14
반응형

Java Stream

스트림은 JAVA8 API 에 새로추가된 기능이다.스트림을 이용하면 선언형 (즉, 데이터를 처리하는 임시 구현코드 대신 질의로 표현할 수 있다) 으로 컬렉션 데이터를 처리 할 수있다. 일단 스트림이 데이터 컬렉션 반복을 멋지게 처리하는 기능이라고 생각하자. 또한 스트림을 이용하면 멀티스레드 코드를 구현하지 않고도 데이터를 투명하게 병렬로 처리할 수있다. → morden java in action

Stream 의 여러 기능중 filter, sorted, map , collect 같은 여러 빌딩 블록 연산을 연결해서 복잡한 데이터 처리 의 파이프라인을 만들어 낼수있다. 여러 연산을 파이프라인으로 연결해도 여전히 가동성과 명확성이 유지된다.

filter와(sorted, map, collect) 같은 연산은 고수준 빌딩 블록으로 이루어져있으므로 특정 스레딩 모델에 제한되지 않고 자유롭게 어떤 상황에서든지 사용 할 수있다. (또한 이들은 내부적이로 단일 쓰레드 모델에서사용 할 수있지만 멀티코어 아키텍처를 최대한 투명하게 사용할수있게 구현되어 있다) 결과적으로 우리는 데이터 처리 과정을 병렬화하면서 쓰레드와 락을 걱정할 필요가 없어졌다.

자바8의 스트림 API 의 특징을 다음처럼 요약할 수있다.

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

Stream 과 Collection의 다른점

  • Stream은 내부 반복을 사용하고 , Collection은 외부반복을 사용한다.
  • Stream 은 연산처리에 집중되어 있고, Collection은 데이터 저장및 삭제등 시간과 공간복잡도에 집중되어 있다.

외부반복과 내부반복의 차이점

코드로 먼저 살펴보자. 내부반복을 하기위해서는 froeach 문이나 Iterator을 사용한다.

예제) 외부반복

List<String> names = new ArrayList<>();
for (Dish dish: menu) {     <-  메뉴리스트를 명시적으로 순차 반복한다. 
    names.add (dish.getName())
}

외부반복은 명시적으로 컬렉션 항목을 하나씩 가져와서 처리한다.

예제) 내부반복

List<String> names = menu.stream()
.map(Dish::name)
.collect(Collection.toList());

map() 파이프라인을 실행하며 내부반복을 실행함으로 반복자는 필요치 않는다.

일련의예로 풀이해보자. 소피아와, 마리오의 대화

마리오 : “소피아 . 장난감좀 정리하렴. 방바닥에 장난감있지?”

소피아 : “네, 공이 있어요”

마리오 : “좋아, 그럼공을 상자에 담자. 또 어떤장난감이 있지?”

소피아 : “인형이 있어요”

마리아 : “그럼 인형을 상자에 담자, 또 어떤장난감이 있지?”

소피아 : “책이 있어요”

…. 생략

마리아 : “OOO 을 상자에 담자, 또 어떤장난감이 있지?”

소피아 : “아무것도 없어요”

마리아 : “참잘했어”

위대화는 자바 컬렉션을 사용하는것과 같다. 컬렉션은 외부반복 즉 명시적으로 컬렉션 항목을 하나씩 가져와 처리하는 방법이다. “소피아에게 바닥에 있는 장난감을 모두 담아라” 라고 이야기하면 내부반복을 사용하여 이점을 얻을수있다. 모든장난감을 상자에 담을 것이고, 양손을 이용하여 동시에 정리 할 수 있다.

이처럼 스트림의 내부반복을 이용하면 작업을 투명하게 병렬로 처리하거나 더 최적화된 다양한 순서로 처리할수있다. 반면 기존 for-each 를 사용하는 외부 반복에서 병렬성을 처리한다면 스스로 관리해야 한다.

스트림 연산

스트림인터페이스는 많은 연산을 정의 한다. 그중에서도 스트림인터페이스의 연산은 크게 중간연산최종연산 두가지로 구분할 수있다.

long count = menu.stream()
            .filter(dish-> dish.getCalories() > 300).  <- 중간연산
            .map(Dish::getNAme).                       <- 중간연산
            .limit()                                   <- 중간연산
            .collect(toList()).      <- 최종연산, 스트림을 리스트로 변환

 

중간연산

filter나 sorted 같은 중간연산자들은 새로운 스트림을 반환한다. 따라서 여러 중간연산자들을 연결하여 새로운 질의를 만들어 낼수있다. 중간연산자의 특징으로는 단말 연산을 스트림 파이프라인에서 실행하기 전까지는 아무 작업도 수행하지 않는다. 즉게으르다(lazy)는 것이다. 중간연산자를 여러개 넣고나면 중간연산자들끼리 합친다음에 최종연산으로 한번에 처리하기 때문이다. (이게 도대체 무슨말이냐…)

최종연산

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

연결할수있는 스트림 연산을 중간연산이라고 하고, 스트림을 닫고 최종결과를 도출하는 연산을 최종연산이라고 한다.

스트림 특징

  • 스트림은 소스에서 추출된 연속 요소로 데이터 처리 연산을 지원한다.
  • 스트림은 내부반복자를 지원한다. 내부반복은 filter, map, sorted 등의 연산으로 반복을 추상화 한다.
  • 중간연산자를 사용하여 스트림을 반환하면서 다른 연산과 연결되는 연산을만들어 낸다. 즉 중간연산을 이영햇 파이프라인을 구축할수있다. 단 중간연산만으로는 어떠한 결과물도 얻을수없다.
  • 최종연산을 요청해서 스트림이아닌 결과를 반환하는 연산을 수행해야한다.
  • 스트림의 요소는 요청할때 게으르게 실행된다.
반응형