[모던 자바 인 액션] Chapter05. 스트림 활용

SunYerim·2024년 2월 2일
0

언어

목록 보기
5/11

5.1 필터링

스트림의 요소를 선택하는 방법, 즉 프레디케이트 필터링 방법과 고유 요소만 필터링하는 방법을 배운다.

5.1.1 프레디케이트로 필터링

스트림 인터페이스는 filter 메서드를 지원하는데, 해당 메서드에서는 프레디케이트(불리언을 반환하는 함수)를 인수로 받아서 프레디케이트와 일치하는 모든 요소를 포함하는 스트림을 반환한다.

List<Dish> vegetarianMenu = menu.stream()
																.filter(Dish::isVegetarian)
																.collect(toList());

5.1.2 고유 요소 필터링

스트림은 고유 요소로 이루어진 스트림을 반환하는 distinct 메서드도 지원한다. (고유 여부 → 객체의 hashCode, equals로 결정됨.)

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, dropWhile 두 가지 메서드를 지원한다.

  • TAKEWHILE 활용
    List<Dish> slicedMenu1
    	= specialMenu.stream()
    							 .takeWhile(dish -> dish.getCalories() < 320)
    							 .collect(toList());
    • filter 스트림을 활용하면 전체 스트림을 반복하면서 각 요소에 프레디케이트를 적용하게 된다. 이미 정렬되어 있다고 가정.
    • takewhile을 이용하면 무한 스트림을 포함한 모든 스트림에 프레디케이트를 적용해 스트림을 슬라이스할 수 있다.
  • DROPWHILE 활용
    List<Dish> slicedMenu2
    	= specialMenu.stream()
    							 .dropWhile(dish -> dish.getCalories() < 320)
    							 .collect(toList());
    • dropwhile은 takewhile과 정반대의 작업을 수행한다.
    • dropwhile은 프레디케이트가 처음으로 거짓이 되는 지점까지 발견된 요소를 버린다.
    • 프레디케이트가 거짓이 되면 그 지점에서 작업을 중단하고 남은 모든 요소를 반환한다.
    • 무한한 남은 요소를 가진 무한 스트림에서도 동작한다.

5.2.2 스트림 축소

스트림은 주어진 값 이하의 크기를 갖는 새로운 스트림을 반환하는 limit(n)메서드를 지원한다.

스트림이 정렬되어 있으면 최대 요소 n개를 반환할 수 있다.

정렬되지 않아도 사용이 가능하다.

5.2.3 요소 건너뛰기

스트림은 처음 n개 요소를 제외한 스트림을 반환하는 skip(n) 메서드를 지원한다. n개 이하의 요소를 포함하는 스트림에 skip(n)을 호출하면 빈 스트림이 반환된다.

limit(n)과 skip(n)은 상호 보완적인 연산을 수행한다.


5.3 매핑

스트림 API의 map과 flatMap 메서드는 특정 데이터를 선택하는 기능을 제공한다.

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

스트림은 함수를 인수로 받는 map 메서드를 지원한다.

List<String> words = Arrays.asList("Modern", "Java", "In", "Action");
List<Integer> wordLengths = words.stream()
																.map(String::length)
																.collect(toList());

각 요리명의 길이를 알고싶다면? → 다른 map 메서드를 연결할 수 있다.

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

5.3.2 스트림 평면화

map을 이용해서 각 단어의 길이 반환을 하였고, 이를 응용해서 리스트에서 고유 문자로 이루어진 리스트를 반환해본다.

words.stream()
		 .map(word -> word.split(""))
		 .distinct()
		 .collect(toList());

위의 map 메소드가 반환한 스트림의 형식은 Stream<String[]>이다. 우리가 원하는 것은 문자열의 스트림을 표현할 Stream이다. ⇒ flatMap을 활용하여 해결할 수 있다.

  • map과 Arrays.stream활용
    String[] arrayOfWords = {"Goodbye", "World"};
    Stream<String> streamOfWords = Arrays.stream(arrayOfWords);
    
    words.stream()
    		 .map(word -> word.split("")) // 각 단어를 개별 문자열 배열로 반환
    		 .map(Arrays::stream) // 각 배열을 별도의 스트림으로 생성
    		 .distinct()
    		 .collect(toList());
    • 배열 스트림 대신 문자열 스트림이 필요하다.
    • 문자열을 받아 스트림을 만드는 Arrays.stream() 메서드가 있다.
    • 결국 스트림 리스트가 만들어지면서 문제가 해결되지 않았다.
  • flatMap사용
    List<String> uniqueCharacters =
    	words.stream()
    			 .map(word -> word.split("")) // 각 단어를 개별 문자를 포함하는 배열로 변환
    			 .flatMap(Arrays::stream) // 생성된 스트림을 하나의 스트림으로 평면화
    			 .distinct()
    			 .collect(toList());
    • flatMap은 각 배열을 스트림의 콘텐츠로 매핑한다. 즉, 하나의 평면화된 스트림을 반환한다.
    • flatMap 메서드는 스트림의 각 값을 다른 스트림으로 만든 다음에 모든 스트림을 하나의 스트림으로 연결하는 기능을 수행한다.

5.4 검색과 매칭

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

anyMatch 메서드를 이용하면된다.

if (menu.stream().anyMatch(Dish::isVegetarian)) {
			System.out.println("The menu is (somewhat) vegetarian friendly!!");
}

anyMatch는 불리언을 반환하므로 최종 연산이다.

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

allMatch는 스트림의 모든 요소가 주어진 프레디케이트와 일치하는지 검사한다.

boolean isHealthy = menu.stream()
												.allMatch(dish -> dish.getCalories() < 1000);
  • NONEMATCH
    • allMatch와 반대 연산을 수행한다.

anyMatch, allMatch, noneMatch 세 메서드는 스트림 쇼트서킷기법, 즉 자바의 &&, ||와 같은 연산을 활용한다.

5.4.3 요소 검색

Optional이란?

  • Optional 클래스는 값의 존재나 부재 여부를 표현하는 컨테이너 클래스다.
  • 값이 존재하는지 확인하고 값이 없을 때 어떻게 처리할지 강제하는 기능을 제공한다.

5.4.4 첫 번째 요소 찾기

List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree = 
		someNumbers.stream()
							 .map(n -> n * n)
							 .filter(n -> n % 3 == 0)
							 .findFirst();

5.5 리듀싱

리듀스 연산을 이용해서 스트림 요소를 조합하여 더 복잡한 질의를 표현하는 방법을 설명한다.

이를 수행하려면 Integer 같은 결과가 나올 때까지 스트림의 모든 요소를 반복적으로 처리해야하는데, 이런 질의를 리듀싱 연산이라고 한다.

5.5.1 요소의 합

리스트의 숫자 요소를 더하는 코드.

리스트에서 하나의 숫자가 남을 때까지 reduce 과정을 반복하는데, 두 개의 파라미터를 사용한다.

  • sum 변수의 초깃값 0
  • 리스트의 모든 요소를 조합하는 연산(+)

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

int sum = numbers.stream().reduce(0, (a, b) -> a + b);

reduce는 두 개의 인수를 갖는다.

  • 초깃값 0

두 요소를 조합해서 새로운 값을 만드는 BinaryOperator

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

초깃값 없음

초깃값을 받지 않도록 오버로드된 reduce도 있으나 Optional 객체를 반환한다.

5.5.2 최댓값과 최솟값

reduce를 활용해서 스트림에서 최댓값과 최솟값을 찾을 수도 있다. 이때 reduce는 두 인수를 받는다.

  • 초깃값
  • 스트림의 두 요소를 합쳐서 하나의 값으로 만드는 데 사용할 람다
Optional<Integer> max = numbers.stream().reduce(Integer::max) // 스트림의 최댓값
Optional<Integer> min = numbers.stream().reduce(Integer::min) // 스트림의 최솟값

5.6 실전 연습


5.7 숫자형 스트림

int calories = menu.stream()
									 .map(Dish::getCalories)
									 .reduce(0, Integer::sum);

위의 코드에는 박싱 비용이 숨어있다. 내부적으로 합계를 계산하기 전에 Integer를 기본형으로 언박싱해야한다.

map 메서드가 Stream를 생성하기 때문에 sum 메서드를 직접 호출할 수는 없다. 그렇지만 기본형 특화 스트림을 제공함으로써 문제점을 해결할 수 있다.

5.7.1 기본형 특화 스트림

스트림을 특화 스트림으로 변환할 때는 mapToInt, mapToDouble, mapToLong 세 가지 메서드를 가장 많이 사용한다. Stream 대신 특화된 스트림을 반환한다.

int calories = menu.stream()
									 .mapToInt(Dish::getCalories)
									 .sum();

객체 스트림으로 복원하기

아래 코드와 같이 boxed 메서드를 이용해서 특화 스트림을 일반 스트림으로 변환할 수 있다.

IntStream intStream = menu.stream().mapToInt(Dish::getCalories); // 스트림을 숫자 스트림으로 변환
Stream<Integer> stream = intStream.boxed(); // 숫자 스트림을 스트림으로 변환

기본값 : OptionalInt

IntStream에서 최댓값을 찾을 때는 0이라는 기본값 때문에 잘못된 결과가 도출될 수 있다. → 값이 존재하는지 여부를 가리킬 수 있는 컨테이너 클래스 Optional 사용


5.8 스트림 만들기

5.8.1 값으로 스트림 만들기

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

Stream<String> stream = Stream.of("Modern ", "Java ", "In ", "Action"); stream.map(String::toUpperCase).forEach(System.out::println);

Stream<String> emptyStream = Stream.empty() // empty 메서드 사용하여 스트림 비우기.

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

Stream.ofNullable을 이용할 수 있다.

5.8.3 배열로 스트림 만들기

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

int[] numbers = {2, 3, 5, 7, 11, 13};
int sum = Arrays.stream(numbers).sum();

5.8.4 파일로 스트림 만들기

java.nio.file.Files의 많은 정적 메서드가 스트림을 반환한다.

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

함수에서 스트림을 만들 수 있는 두 정적 메서드 Stream.iterate와 Stream.generate를 제공한다. 이를 이용해서 무한 스트림 , 즉 고정된 컬렉션에서 고정된 크기로 스트림을 만들었던 것과는 달리 크기가 고정되지 않은 스트림을 만들 수 있다.

iterate와 generate에서 만든 스트림은 요청할 때마다 값을 만들기때문에 무제한으로 값을 계산할 수 있다. 이를 막고자 limit(n) 함수를 함께 연결하여 사용해준다.

iterate는 요청할 때마다 값을 생산할 수 있으며 끝이 없으므로 무한 스트림을 만든다. 이러한 스트림을 언바운드 스트림이라고도 한다. 이러한 특징이 스트림과 컬랙션의 가장 큰 차이다.

generate도 요구할 때 값을 계산하는 무한 스트림을 만들 수 있으나 생산된 각 값을 연속으로 계산하지는 않는다. generate는 Supplier를 인수로 받아서 새로운 값을 생산한다.

profile
내 안에 있는 힘을 믿어라.

0개의 댓글