티스토리 뷰

4.1 스트림이란 무엇인가?

  • 스트림은 자바 8 API에 새로 추가된 기능
  • 스트림을 이용하면 선언형으로 컬렉션 데이터를 처리할 수 있음.
  • 또한 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리할 수 있음.
// 자바 7 기준
List<Dish> lowCaloricDishes = new ArrayList<>();
for (Dish dish : Dish.menu) {
	if (dish.getCalories() < 400) {
		lowCaloricDishes.add(dish);
	}
}
// 칼로리 적은 순으로 정렬
Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
	@Override
	public int compare(Dish o1, Dish o2) {
		return Integer.compare(o1.getCalories(), o2.getCalories());
	}
});
List<String> lowCaloricDishesName = new ArrayList<>();
for (Dish dish : lowCaloricDishes) {
	lowCaloricDishesName.add(dish.getName());
}
  • 자바 7에서는 저칼로리 (칼로리 400 미만인) 요리명을 반환하고 칼로리를 기준으로 요리를 정렬하는 코드는 위와 같음.
  • 위 코드에서 lowCaloricDishes라는 '가비지 변수'를 사용함. 단지 한번 칼로리를 기준으로 필터링한 결과를 담아주는 중간 변수임. 
List<String> lowCaloricDishesName = Dish.menu.stream()
				.filter(dish -> dish.getCalories() < 400)
				.sorted(comparing(Dish::getCalories))
				.map(Dish::getName)
				.collect(toList());
System.out.println(lowCaloricDishesName);
  • 자바 8에서는 위와 같이 세부 구현은 라이브러리 내에서 모두 처리가 됨.

 

  • 자바 8 스트림의 장점
    • 선언형으로 코드를 구현할 수 있음. 루프와 if 조건문 등의 제어 블록을 사용해서 어떻게 동작할 것인지 지정할 필요 없이 '저칼로리의 요리만 선택하라' 같은 동작의 수행을 지정할 수 있음.
    • filter, sorted, map, collect 같은 여러 빌딩 블록 연산을 연결해서 복잡한 데이터 처리 파이프라인을 만들 수 있음. 여러 연산을 연산을 파이프라인으로 연결해도 가독성, 명확성이 유지됨.
  • filter, sorted, map, collect 같은 연산은 특정 스레딩 모델에 제한되지 않고 자유롭게 사용할 수 있음.
  • 우리는 스트림 API를 통해서, 데이터 처리 과정을 병렬화하면서 스레드와 락을 신경쓰지 않아도 됨.

 

  • 자바 8의 스트림 API 특징
    • 선언형 : 더 간결하고 가독성이 좋아짐
    • 조립할 수 있음 : 유연성이 좋아짐
    • 병렬성 : 성능이 좋아짐

 

4.2 스트림 시작하기

  • 자바 8 컬렉션에는 스트림을 반환하는 stream 메서드가 추가됨.
  • 참고로, 이 외에도 숫자 범위나 I/O 자원에서 스트림 요소를 만드는 등 stream 메서드 이외에도 다양한 방법으로 스트림을 얻을 수 있음.

 

  • 스트림의 정의 -> 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소
    • 연속된 요소 : 스트림은 특정 요소 형식으로 이루어진 연속된 값집합의 인터페이스를 제공함.
    • 소스 : 스트림은 컬렉션, 배열, I/O 자원 등의 데이터 제공 소스로부터 데이터를 소비함. 정렬된 컬렉션으로 스트림을 생성하면 정렬이 그대로 유지됨.
    • 데이터 처리 연산 : 스트림은 함수형 프로그래밍 언어에서 일반적으로 지원하는 연산과 데이터베이스와 비슷한 연산을 지원함. (filter, map, reduce, find, match 등으로 데이터 조작 가능). 또한 스트림 연산은 순차적으로 또는 병렬로 실행할 수 있음
  • 스트림의 특징
    • 파이프라이닝 : 대부분의 스트림 연산은 스트림 연산끼리 연결해서 커다란 파이프라인을 구성할 수 있도록 스트림 자신을 반환함. (그로 인해 Jaziness (게으름), shor-circuiting(쇼트서킷) 같은 최적화도 얻을 수 있음) 
    • 내부 반복 : 반복자를 이용해서 명시적으로 반복하는 컬렉션과 달리 스트림은 내부 반복을 지원함.

 

List<String> threeHighCaloricDishNames = Dish.menu.stream()
						.filter(dish -> dish.getCalories() > 300)
						.map(Dish::getName)
						.limit(3)
						.collect(toList());
  • 위 코드에서 데이터 소스는 메뉴임. 데이터 소스는 연속된 요소를 스트림에 제공함.
  • 다음으로 스트림에 filter, map, limit collect로 이어지는 일련의 데이터 처리 연산을 적용함. collect를 제외한 모든 연산은 서로 파이프라인을 형성할 수 있도록 스트림을 반환함.
  • 마지막으로 collect 연산으로 파이프라인을 처리해서 결과를 반환함.
  • 마지막에 collect를 호출하기 전까지는 menu에서 무엇도 선택되지 않으며 출력 결과도 없음. 즉, collect가 호출되기 전까지는 메서드 호출이 저장되는 효과가 있음.

 

4.3 스트림과 컬렉션

  • 자바의 기존에 존재하는 컬렉션과 새롭게 추가된 스트림 모두 연속된 요소 형식의 값을 저장하는 자료구조의 인터페이스를 제공하지만, 데이터를 언제 계산하는냐가 컬렉션과 스트림의 가장 큰 차이임.
  • 컬렉션은 현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 자료구조임. 즉, 컬렉션의 모든 요소는 컬렉션에 추가하기 전에 계산되어야 함.
  • 반면 스트림은 이론적으로 요청할 때마 요소를 계산하는 고정된 자료구조임.

 

4.3.1 딱 한 번만 탐색할 수 있다

  • 반복자와 마찬가지로 스트림도 한 번만 탐색할 수 있음. 즉, 탐색된 스트림의 요소는 소비됨.
  • 반복자와 마찬가지로 한 번 탐색한 요소를 다시 탐색하려면 초기 데이터 소스에서 새로운 스트림을 만들어야 함.

 

4.3.2 외부 반복과 내부 반복

  • 컬렉션 인터페이스를 사용하려면 사용자가 직접 요소를 반복해야 함 -> 외부 반복
  • 반면, 스트림 라이브러리는 내부 반복을 사용함. 함수에 어떤 작업을 수행할지만 지정하면 알아서 처리됨.
List<String> names = new ArrayList<>();
for (Dish dish : Dish.menu) {
	names.add(dish.getName());
}
List<String> names = Dish.menu.stream()
			.map(Dish::getName)
                    	.collect(toList());
  • 내부 반복을 이용하면 작업을 투명하게 병렬로 처리하거나 더 최적화된 다양한 순서로 처리할 수 있음.
  • 스트림 라이브러리 내부 반복은 데이터 표현과 하드웨어를 활용한 병렬성 구현을 자동으로 선택함.
  • 그러나 for-each를 이용하는 외부 반복의 경우 병렬성을 스스로 관리해야 함.

 

4.4 스트림 연산

  • java.util.stream.Stream 인터페이스는 많은 연산을 정의함.
  • 스트림 인터페이스의 연산을 크게 두 가지로 구분할 수 있음.
    • 중간 연산 : 연결할 수 있는 스트림 연산
    • 최종 연산 : 스트림을 닫는 연산

 

4.4.1 중간 연산

  • filter나 sorted 같은 중간 연산은 다른 스트림을 반환함. 따라서 여러 중간 연산을 연결해서 질의를 만들 수 있음.
  • 중간 연산의 중요한 특징은 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않는다는 것, 즉 게으르다(Lazy)는 것임.
  • 중간 연산을 합친 다음에 합쳐진 중간 연산을 최종 연산으로 한 번에 처리하기 때문.

 

4.4.2 최종 연산

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

 

4.4.3 스트림 이용하기

  • 스트림 이용 과정
    • 질의를 수행할 (컬렉션 같은) 데이터 소스
    • 스트림 파이프라인을 구성할 중간 연산 연결
    • 스트림 파이프라인을 실행하고 결과를 만들 최종 연산

 

4.6 마치며

  • 스트림은 소스에서 추출된 연속 요소로, 데이터 처리 연산을 지원함.
  • 스트림은 내부 반복을 지원함. 내부 반복은 filter, map, sorted 등의 연산으로 반복을 추상화함.
  • 스트림에는 중간 연산과 최종 연산이 있음.
  • 중간 연산은 filter와 map처럼 스트림을 반환하면서 다른 연산과 연결되는 연산임. 중간 연산을 이용해서 파이프라인을 구성할 수 있지만 중간 연산으로는 어떤 결과도 생성할 수 없음.
  • forEach나 count처럼 스트림 파이프라인을 처리해서 스트림이 아닌 결과를 반환하는 연산을 최종 연산이라고 함.
  • 스트림의 요수는 요청할 때 Lazy하게 계산됨.
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
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
글 보관함