[모던 자바 인 액션] Chapter 5 스트림 활용

OhJuYeong·2025년 12월 19일

모던 자바 인 액션

목록 보기
4/9
post-thumbnail

5.1 필터링

스트림의 요소를 선택하는 방법에 대해 배움

5.1.1 프레디케이트로 필터링

filter 메서드

  • 프레디케이트를 인수로 받아서 프레디케이트와 일치하는 모든 요소를 포함하는 스트림을 반환
List<Dish> vegetarianMenu = menu.stream().filter(Dish::isVegetarian).collect(toList());

5.1.2 고유 요소 필터링

고유 요소로 이루어진 스트림을 반환하는 distinct 메서드도 지원

고유 결정 여부

  • 스트림에서 만든 객체의 hashCode, equals로 결정

ex) 리스트의 모든 짝수를 선택하고 중복을 필터링

List<Integer> numbers = Arrays.asList(1,2,1,3,3,2,4);
numbers.stream()
			 .filter(i -> i%2 ==0)
			 .distinct()
			 .forEach(System.out::println)

5.2 스트림 슬라이싱

5.2.1 프레디케이트를 이용한 슬라이싱

TakeWhile 활용

List<Dish> filteredMenu = specialMenu.stream()
.filter(dish -> dish.getCalories()< 320)
.collect(toList());
  • filter 연산을 이용하면 전체 스트림을 반복하면서 각 요소에 프레디케이트를 적용

→ 리스트가 이미 정렬되어 있다는 사실을 이용해 320 칼로리보다 크거나 같은 요리가 나왔을 때 반복 작업을 중단 할 수 있음

  • 큰 스트림에서 상당한 차이

→ takeWhile 이용하면 무한 스트림을 포함한 모든 스트림에 프레디케이트를 적용해 스트림 슬라이스 가능

List<Dish> sliceMenu1 = specialMenu.stream()
.takeWhile(dish -> dish.getCalories <320)
.collect(toList());

DROPWHILE 활용

나머지 요소를 선택하려면?
→dropWhile 사용

List<Dish> sliceMenu2 = specialMenu.stream()
.dropWhile(dish -> dish.getCalories() <320)
.collect(toList());
  • dropWhile은
    • takeWhile 정반대의 작업 수행
    • 프레디케이트가 처음으로 거짓이 되는 지점까지 발견된 요소를 버림
    • 프레디케이트가 거짓이 되면 그 지점에서 작업을 중단하고 남은 모든 요소를 반환
    • 무한한 남은 요소를 가진 무한 스트리미에서도 동작

5.2.2 스트림 축소

스트림

  • 주어진 값 이하의 크기를 갖는 새로운 스트림을 반환하는 limit(n) 메서드를 지원
  • 정렬 되어있으면 최대 요소 n개를 반환
  • 정렬 안되어있으면 정렬되지 않은 상태로 반환
List<Dish> dishes = specialMenu.stream()
.filter(dish -> dish.getCalories() > 300)
.limit(3)
.collect(toList());

5.2.3 요소 건너 뛰기

스트림

  • 처음 n개의 요소를 제외한 스트림을 반환하는 skip(n) 메서드를 지원
  • n개 이하의 요소를 포함하는 스트림에 skip(n)을 호출하면 빈 스트림이 반환
  • limit(n)과 skip(n)은 상호 보완적인 연산 수행

ex) 300 칼로리 이상의 처음 두 요리를 건너뛴 다음에 300 칼로리가 넘는 나머지 요리를 반환

List<Dish> dishes = menu.stream()
.filter(d ->< d.getCalories() > 300)
.skip(2)
.collect(toList());

5.3 매핑

5.3.1 스트림의 각 요소에 함수 적용하기

스트림

  • 함수를 인수로 받는 map 메서드를 지원
  • 인수로 제공된 함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소 매핑
  • 새로운 버전은 만든다
  • 변환에 가까운 매핑

ex) Dish::getName을 map 메서드로 전달해 스트림의 요리명 추출

List<String> dishNames = menu.stream()
.map(Dish::getName)
.collect(toList());

getName

  • 문자열 반환

→ map 메서드 출력 스트림 Strem 형식

5.3.2 스트림 평면화

고유 문자로 이루어진 리스트를 반환

ex) [”Hello”] → [”H”,”e”,”l”,”o”]

  • 리스트에 있는 각 단어를 문자로 매핑 후 중복된 문자를 필터링 하면 해결?!
words.stream()
		.map(word -> word.split("")
		.distint()
		.collect(toList());

→ 반환 형식이 문제

  • 기대 Stream 현실 Stream<String[]>
  • 해결 법 flatMap 사용하기

map 과 Arrays.Stream 활용

  • 배열 스트림 대신 문자열 스트림 필요
  • 문자열을 받아 스트림으로 만드는 Arrays.stream()

→ 안됨 스트림 리스트가 만들어지면서 문제 해결이 안됨

flatMap 사용

List<String> uniqueCharacters= words.stream()
  .map(word -> word.split(""))
  .flatMap(Arrays::stream)
  .distinct()
  .collect(toList());

flatMap

  • 배열을 스트림이 아니라 스트림의 콘텐츠로 매핑
  • 하나의 평면화된 스트림 반환

5.4 검색과 매칭

5.4.1 프레디게이트가 적어도 한 요소와 일치하는 지 확인

  • anyMatch 메서드 이용
  • 불리언을 반환

5.4.2 프레디케이트가 모든 요소와 일치하는지 검사

  • allMatch 메서드 사용
  • nonMatch
    • allMatch와 반대 연산
    • 프레디케이트와 일치하는 요소가 없는지 확인

→ 세 메서드는 스트림 쇼트서킷 기법 , 즉 자바의 &&, || 와 같은 연산

5.4.3 요소검색

  • findAny 메서드로 현재 스트림 에서 임의의 요소를 반환
  • 다른 스트림연산과 연결해서 사용
Optional<Dish> dish = menu.stream().filter(Dish::isVegetarian).findAny();

Optional이란?

Optional

  • 값의 존재나 부재 여부를 표현하는 컨테이너 클래스
  • isPresent()
    • Optional 이 값을 포함하면 참을 반환 , 포함하지 않으면 거짓
  • ifPresent 는 값이 있으면 주어진 블록 수행
  • T get() 값이 존재하면 값을 반환 없으면 NoSuchElementException을 일으킴
  • T orElse(T other) 는 값이 있으면 값 반환 없으면 기본값 반환

5.4.4 첫 번째 요소 찾기

  • 리스트, 정렬된 연속 데이터로부터 생성된 일부 스트림에서 논리적인 아이템 순서가 정해져있을 수 있음

5.5 리듀싱

리듀스 연산 이용

5.5.1 요소의 합

reduce를 이용해서 스트림의 모든 요소를 더할 수 있음

int sum = numbers.stream().reduce(0,(a,b) -> a+b);
  • 초깃값 0
  • 두 요소를 조합해 새로운 값을 만드는 BinaryOperator
  • (a,b) → a* b를 넘겨주면 모든 요소에 곱셈 적용 가능

메서드 참조 이용하면 더 간결

int sum = numbers.stream().reduce(0,Integer::sum);

초깃값 없음

  • 초깃값을 받지 않도록 오버로드된 reduce도 있음
  • Optional 객체를 반환
Optional<Integer> sum = numbers.stream().reduce((a,b) -> (a+b));
  • 합계가 없음을 가르킬 수 있도록 Optional 객체로 감싼 결과 반환

5.5.2 최댓값과 최솟값

  • 이때도 reduce 사용
  • 방법
    • 두 인수를 받음

      • 초깃값
      • 스트림의 두요소를 합쳐서 하나의 값으로 만드는데 사용할 람다
    • 두 요소에서 최댓값을 반환하는 람다만 있으면 구할 수 있음

      → reduce 연산은 새로운 값을 이용해서 스트림의 모든 요소를 소비할 때까지 람다를 반복수행해서 최댓값 생산

Optional<Integer> max = numbers.stream().reduce(Integer::max);

5.6 실전 연습

알아서 다른곳에 풀겠다.

5.7 숫자형 스트림

스트림 API

  • 숫자 스트림을 효율적으로 처리할 수 있도록 기본 특화 스트럼 제공

5.7.1 기본형 특화 스트림

  • 박싱 비용을 피할 수 있도록 int 요소에 특화된 IntStream, double 요소에 특화된 DoubleStream , long 요소에 특화된 LongStream 제공

특화스트림

  • 오직 박싱 과정에서 일어나는 효율성과 관련 있으며 스트림에 추가 기능을 제공하지 않는다는 사실을 기억

숫자 스트림으로 매핑

  • mapToInt, mapToDouble,mapToLong 세가지 메서드 많이 사용
  • map과 정확히 같은 기능을 수행 하지만, 특화된 스트림 반환
int calories = menu.stream() // Stream<Dish> 반환
									.mapToInt(Dish::getCalories) //IntStream 반환
									.sum();
  • mapToInt
    • 모든 칼로리를 추출한 다음에 IntStream을 반환
    • sum 메서드를 이용해 칼로리 합계 계산 가능
    • 스트림이 비어있으면 기본값 0을 반환

객체 스트림으로 복원하기

  • 숫자 스트림 만든 후 복원할 수 있을까?
  • IntStream은 기본형의 정수값만 들 수 있어서 IntStream의 map 연산은 int 를 인수로 받아서 int를 반환하는 람다를 인수로 받음
  • 다른 값을 반환하고 싶으면?
    • 스트림 인터페이스에 정의된 일반적인 연산 사용해야함

기본값: OptionalInt

스트림에 요소가 없는 상화과 실제 최댓값이 0인 상황 구별 법

  • 값의 존재 여부를 알 수 있는 Optional 클래스 이용

5.7.2 숫자 범위

ex) 1에서 100 사이의 숫자를 생성 가정

  • IntStream과 LongStream 에서 range, rangeClosed 정적 메서드 제공
  • 두 메서드 첫번 째 인수 시작값, 두번째 인수 종료값 가짐
  • range은 시작값, 종료값이 결과에 포함 되지 않음 rangeClosed 는 포함됨
IntStream evenNumbers= IntStream.rangeClosed(1,100)
//[1,100]의 범위를 나타냄

5.7.3 숫자 스트림 활용 : 피타고라스 수

수학이 자꾸 나오네;;

세수 표현하기

  • int 배열 사용
    • new int[]{3,4,5} 표현가능

좋은 필터링 조합

  • 세 수 중 a,b 두 수만 제공했다고 가정
  • aa + bb 의 제곱근이 정수인지 확인할 수 있음
  • Math.sqrt(aa + bb) % 1 ==0;
  • filter ( b →Math.sqrt(aa + bb) % 1 ==0)

집합 생성

필터를 이용해 좋은 조합을 갖는 a,b를 선택

세번 째 수 찾는 법

stream.filter( b →Math.sqrt(a*a + b*b) % 1 == 0)
			.map(b -> new int[]{a,b,(int) Math.sqrt(a*a*+b*b)});

b값 생성

Intstream.rangeClosed(1,100)
			.filter( b →Math.sqrt(a*a + b*b) % 1 == 0)
			.boxed()
			.map(b -> new int[]{a,b,(int) Math.sqrt(a*a*+b*b)});
  • filter 연산 후 rangeClosed가 반환한 IntStream을 boxed를 이용해 Stream 로 복원
  • map은 스트림의 각 요소를 int 배열로 반환

a 값 생성

Stream<intp[> pythagoreanTriples = IntStream.rangeClosed(1,100).boxed()
  .flatMap( a-> IntStream.rangeClosed(a,100)
  .filter(b -> Math.sqrt(a*a +b*b) % 1 == 0)
  .mapToObj(b -> new int[]{a,b,(int)Math.sqrt(a*a+bb*b)}))

5.8 스트림 만들기

5.8.1 값으로 스트림 만들기

  • 임의의 수를 인수로 받는 정적 메서드 Stream.of를 이용해 스트림 만들 수 있음

5.8.2 null이 될 수 있는 객체로 스트림 만들기

  • System.getProperty는 제공된 키에 대응하는 속성이 없으면 널 반환
  • 이런 메서드를 활용 하기 위해 널을 명시적으로 확인
  • flatMap과 함께 사용하면 더 유용하게 사용 가능

5.8.3 배열로 스트림 만들기

  • 배열을 인수로 받는 정적 메서드 Arrays.stream을 이용해 스트림 만들 수 있음

5.8.4 파일로 스트림 만들기

  • 파일을 처리하는 등의 I/O 연산에 사용하는 자바의 NIO API도 스트림 API를 활용할 수 있도록 업데이트

5.8.5 함수로 무한 스트림 만들기

  • Stream.iterate와 Stream.generate를 제공
  • 두 연산을 이용해 무한 스트림 즉 고정된 컬렉션에서 고정된 크기로 스트림을 만들었던 것과는 달리 크기가 고정되지 않은 스트림을 만들 수 있음
  • iterate와 generate에서 만든 스트림은 요청할때마다 주어진 함수를 이용해 값을 만듦

→ 무제한 값 계산 가능 하지만 보통 limit(n) 함수를 사용해 연결

iterate 메서드

Stream.iterate(0,n->n+2)
			.limit(10)
			.forEach(System.out::println);
  • 초깃값 과 람다를 인수로 받아 새로운 값 끊임없이 생산
  • 무한 스트림 이런 스트림은 언바운드 스트림 이라고 표현

generate 메서드

  • iterate과 비슷하게 값을 계산하는 무한 스트림 만들 수 있음
  • 생산된 각 값을 연속적으로 계산하진 않음
  • Supplier를 인수로 받아 새로운 값을 생성
//1번
List<Transcation> allTranscation = transactions.stream()
  .filter(transaction-> transaction.getYear() ==2011)
  .sorted(comparing(Transcation::getValue))
  .collect(toList())
//2번																					
List<Transcation> allCity = transactions.stream()
  .map(transaction ->transaction.getTrader().getCity())
  .distinct()
  .collect(toList());
				
List<Trader> allCam = transactions.stream()
  .map(Transcation::getTrader) 
  .filter(trader -> trader.getCity().equals("Cambridge"))
  .distinct()
  .sorted(comparing(Trader::getName))
  .collect(toList());
									
profile
기록하는 개발자

0개의 댓글