모던 자바 인 액션 5장: 스트림 활용

Adam·2024년 6월 24일
0

모던 자바 인 액션

목록 보기
5/20

필터링

프레디케이트로 필터링

filter 메서드는 프레디케이트를 인수로 받아 프레디케이트와 일치하는 모든 요소를 포함하는 스트림을 반환

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

위 예시에서 프레디케이트(불리언을 반환하는 함수) isVegetarian을 참조해 필터 진행

고유 요소 필터링

distinct(hascode나 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);

스트림 슬라이싱

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

스트림 요소를 효과적으로 선택할 수 있게 takeWhile, dropWhile 지원

List<Dish> specialMenu = Arrays.asList(
new Dish("fruit"),true, 120, Dish.Type.OTHER),
new Dish("prawns"),false, 300, Dish.Type.FISH),
new Dish("rice"),true, 350, Dish.Type.OTHER),
new Dish("chicken"),false, 400, Dish.Type.MEAT),
new Dish("fries"),true, 530, Dish.Type.OTHER),
);
  • takeWhile: 프레디케이트가 처음으로 참이 되는 지점까지 선택
List<Dish> slicedMenu1 = specialMenu.stream().takeWhile(dish -> dish.getCalories()<320).collect(toList());

결과: 리스트 처음부터 320 칼로리가 적은 음식들만 반환(fruit, prawns)

  • dropWhile: 프레디케이트가 처음으로 거짓이 되는 지점까지 발견 요소를 버린다
List<Dish> slicedMenu2 = specialMenu.stream().dropWhile(dish -> dish.getCalories()<320).collect(toList());

결과: 프레디케이트가 거짓이 되면 그 지점에서 작업 중단 후 남은 요소 반환(rice, chickem, fries), 무한 스트림에서도 동작

스트림 축소

limit(n) 메서드를 써서 n개의 요소를 반환할 수 있다

List<Dish> dishes = specialMenu.stream().filter(dish -> dish.getCalories()>300).limit(3).collect(toList());

결과: rich, chicken, fries 반환

  • 소스가 정렬되지 않았다면 limit 결과값도 정렬되지 않은 상태로 반환

요소 건너뛰기

skip(n)을 사용하면 해당 조건을 만족하는 스트림 값 n개를 건너 뛴 값을 반환

n보다 스트림 결과값이 작다면 빈 스트림을 반환

매핑

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

함수를 인수로 받는 map 메서드를 지원

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

스트림의 평면화

flatMap: 스트림의 각 값을 다른 스트림으로 만든 다음에 모든 스트림을 하나의 스트림으로 연결

검색과 매칭

  • anyMatch: 프레디케이트가 주어진 스트림에서 적어도 한 요소와 일치하는지 확인
  • allMatch: 스트림의 모든 요소가 프레디케이트와 일치하는지 검사
  • noneMatch: 주어진 프레디케이트와 일치하는 요소가 없는지 확인
  • findAny: 현재 스트림에서 임의 요소를 반환, 다른 스트림의 연산과 연결하여 사용 가능
  • findFirst: 정렬된 연속 데이터에서 첫 번째 요소를 반환

리듀싱

모든 스트림 요소를 처리해 값으로 도출하는 작업

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

reduce는 두가지 인수를 갖는다

  1. 초깃값
  2. 두 요소를 조합해서 새로운 값을 만드는 람다

reduce 객체에서 초깃값을 안받는 경우도 있는데 이 때는 빈 스트림일 수 있기 때문에 반환값이 Optional이 된다

최댓값과 최솟값

마찬가지로 reduce 함수에 다음과 같이 넣어서 구할 수 있다

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

reduce 장점

내부 반복이 추상화 되면서 내부 구현에서 병렬로 reduce를 실행할 수 있음(stream을 parallelStream()으로 바꾼다)

하지만 병렬로 실행이 되려면 reduce에서 넘겨준 람다의 상태가 바뀌지 말아야 하며, 연산이 어떤 순서로 실행되더라도 결과가 바뀌지 않는 구조여야 한다.

상태 없음과 상태 있음

  • 내부 상태 없는 연산: map, filter 같이 입력 스트림에서 각 요소를 받아 0 또는 결과를 출력 스트림으 보내는 역할만 한다
  • 내부 상태 한정: reduce, max, sum 같이 결과를 누적할 내부 상태가 필요되어 있고 스트림에서 처리하는 요소 수와 관계 없이 내부 상태의 크기는 한정
  • 내부 상태를 갖는 연산: sorted, distinct 같이 과거 이력을 알고 있어야 해서 모든 요소가 버퍼에 추가되어 있어야 하는 연산

숫자형 스트림

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

IntStream, DoubleStream, LongStream을 제공하는데 숫자 연산에서 사용하는 sum, max와 같은 메서드를 제공

숫자 스트림으로 매핑

mapToInt, mapToDouble, mapToLong을 많이 사용

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

boxed 메서드를 사용해서 특화 되지 않은 일반 스트림으로 변환 가능

IntStream intStream = menu.stream().mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();

range(시작값과 종료값 포함), rangeClosed(시작값과 종료값 불포함)을 사용해 특정 범위 사이의 숫자들을 생성 가능

스트림 만들기

값으로 스트림 만들기

Stream.of을 이용해 스트림을 만들 수 있고 empty 메서드를 이용해 비울 수 있다.

Stream<String> stream = Stream.of("Hello", "World");
Stream<String> empty = Stream.empty();

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

Stream.ofNullable을 이용해 null 값이 될 수 있는 스트림 생성 가능

Stream<String> values = Stream.ofNullable(System.getProperty("home"));

배열로 스트림 만들기

Arrays.stream을 이용해 배열을 인수로 받아 스트림을 만드는 것이 가능

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

파일로 스트림 만들기

파일을 처리하는 NIO API에서 스트림 API를 활용 가능

Files.lines로 파일의 각 행 요소를 반환하는 스트림을 얻는 것이 가능

함수로 무한 스트림 만들기

Stream.iterate, Stream.generate으로 크기가 고정 되지 않은 스트림을 생성할 수 있다

하지만 보통 무한한 값을 출력하지 않도록 limit(n)과 함께 사용

limit을 사용하지 않는다면 최종 연산에서 아무 결과가 반환되지 않으며 무한적으로 계산이 반복되므로 정렬이나 리듀스할 수 없다.

profile
Keep going하는 개발자

0개의 댓글