[Java] Stream 활용

지니🧸·2023년 4월 7일
0

Java

목록 보기
2/13

🎞️ Filtering, 필터링

1. Predicate 필터링

filter 메서드

  • Predicate을 인수로 받아 predicate과 일치하는 모든 요소를 포함하는 스트림 반환
    • predicate: boolean을 반환하는 함수

2. 고유 요소 필터링

distinct 메서드

  • 고유 여부는 스트림에서 만든 객체의 hashCode, equals로 결정됨
List<Integers> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
		.filter(i -> i % 2 == 0)
        .distinct()
        .forEach(System.out::println);

🎞️ Slicing, 슬라이싱

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

1. takeWhile

이미 정렬된 리스트에서는 필터링보다는 takeWhile 함수를 쓰는 것이 효율적임

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

2. dropWhile

  • predicate이 처음으로 거짓이 되는 지점까지 발견된 요소를 모두 버림
  • predicate이 거짓이 되면 그 지점에서 작업을 중단하고 남은 모든 요소를 반환함
  • takeWhile과 정반대의 작업 수행

🎞️ 스트림 축소: Limit

limit(n) 메서드: 최대 요소 n개 반환

🎞️ 요소 건너뛰기: Skip

skip(n): n개의 요소를 건너뜀

  • n개 이하의 요소를 포함하는 스트림에 호출하면 빈 스트림이 반환됨

🎞️ Mapping

🎞️ 스트림의 각 요소에 함수 적용: Map

map

  • 인수로 제공된 함수는 각 요소에 적용됨
  • 함수를 적용한 결과가 새로운 요소로 매핑됨
List<String> dishnames = menu.stream()
							.map(Dish::getName)
                            .collect(toList())

🎞️ 스트림 평면화: flatMap

flatMap 메서드: 하나의 평면화된 스트림 반환

List<String> uniqueCharacters = 
		words.stream()
        	.map(word -> word.split("")) // 각 단어를 개별 문자를 포함하는 배열로 반환
            .flatMap(Arrays::stream) // 생성된 스트림을 하나의 스트림으로 평면화
            .distinct()
            .collect(toList());

🎞️ Matching

🎞️ 적어도 한 요소와 일치하는지: anyMatch

anyMatch: predicate이 주어진 스트림에서 적어도 한 요소와 일치하는지 확인

  • boolean을 반환하므로 최종 연산임
if (menu.stream().anyMatch(Dish::isVegan)) {
	System.out.println("The menu is vegan friendly");
}

🎞️ 모든 요소와 일치하는지: allMatch

allMatch: predicate이 스트림의 모든 요소와 일치하는지

boolean isHealthy = menu.stream()
						.allMatch(dish -> dish.getCalories() < 1000);

🎞️ 모든 요소와 불일치하는지: noneMatch

noneMatch: 주어진 predicate와 일치하는 요소가 없는지 확인

  • allMatch와 반대 연산
boolean isHealthy = menu.stream()
						.noneMatch(d -> d.getCalories() >= 1000);

anyMatch, allMatch, noneMatch는 스트림 쇼트서킷 기법을 사용한다

  • 쇼트서킷: 자바의 &&, ||와 같은 연산
    • 모든 요소를 처리할 필요 없이 원하는 요소를 찾았으면 즉시 결과 반환
    • limit또한 쇼트서킷

🎞️ 요소 검색: findAny

findAny: 현재 스트림에서 임의의 요소를 반환

  • filter와 함께 쓰이면 유용
Optional<Dish> dish = menu.stream()
						.filter(Dish::isVegan)
                        .findAny();

Optional: 값의 존재/부재 여부를 표현하는 컨테이너 클래스

  • findAny는 아무 요소도 반환하지 않을 수 있는데 null은 에러가 일어나기 쉬우므로 Optional 사용
  • 메서드
    • isPresent():
      • Optional이 값을 포함하면 true
      • Optional이 값을 포함하지 않으면 false
    • ifPresent(Consumer<T> block): 값이 있으면 주어진 블록 실행
    • T get():
      • 값이 존재하면 값을 반환
      • 값이 없으면 NoSuchElementException 일으킴
    • T orElse(T other)
      • 값이 있으면 값을 반환
      • 값이 없으면 기본값 반환

🎞️ 첫번째 요소 찾기: findFirst

findFirst vs. findAny

  • 병렬 실행에서는 첫번째 요소를 찾기 어려움
  • 요소 반환 순서가 상관없으면 병렬 스트림에서는 제약이 적은 findAny 사용
Optional<Dish> dish = menu.stream()
						.filter(Dish::isVegan)
                        .findFirst();

🎞️ Reducing, 리듀싱

리듀싱 연산: 모든 스트림 요소를 처리해서 값으로 도출하기

  • 내부 반복이 추상화되면서 내부 구현에서 병렬로 reduce를 실행할 수 있게 됨
    • 반복적인 합계에서는 sum 변수를 공유해야하므로 병렬화가 어려움
    • reduceparallelStream() 사용하면 됨

🎞️ 요소의 합/곱

초기값은 0인 상태에서 요소의 합

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

메서드 참조로 더 간단히 하면

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

초기값은 1인 상태에서 요소의 곱

int product = numbers.stream().reduce(1, (a, b) -> a * b);

초기값 없는 reduce는 Optional을 반환함

Optional<Integer> sum = numbers.stream().reduce((a, b) -> (a + b);

🎞️ 최댓값과 최솟값

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

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

🎞️ 실전

1. 2011년에 일어난 모든 트랜잭션을 찾아 값을 오름차순으로 정렬하시오.

transactions.stream()
			.filter(t -> t.getYear == 2011)
            .sorted(comparing(Transaction::getValue))
            .collect(toList());

2. 거래자가 근무하는 모든 도시를 중복 없이 나열하시오.

transactions.stream()
		.map(t -> t.getTrader().getCity())
        .distinct()
        .collect(toList());

3. 케임브리지에서 근무하는 모든 거래자를 찾아 이름순으로 나열하시오

transactions.stream()
		.map(t -> t.getTrader())
        .filter(t -> t.getCity().equals("Cambridge"))
        .sorted(comparing(Trader::getName))
        .collect(toSet());

4. 모든 거래자의 이름을 알파벳순으로 정렬해서 반환하시오.

transactions.stream()
		.map(t -> t.getTrader().getName())
        .distinct()
        .sorted()
        .collect(joining());
  • joining: 내부적으로 StringBuilder를 이용해 모든 문자열을 반복적으로 연결해 새로운 문자열 객체를 만듬

5. 밀라노에 거래자가 있는가?

transactions.stream()
		.filter(t -> t.getTrader().getCity().equals("Milan")
        .findAny();

6. 케임브리지에 거주하는 거래자의 모든 트랜잭션값을 출력하시오.

transactions.stream()
            .filter(t -> t.getTrader().getCity.equals("Cambridge")
            .map(Transaction::getValue)
            .distinct()
            .forEach(System.out::println);

7. 전체 트랜잭션 중 최댓값은 얼마인가?

transactions.stream()
			.map(Transaction::getValue)
            .reduce(Integer::max);

8. 전체 트랜잭션 중 최솟값은 얼마인가?

transactions.stream()
			.min(comparing(Transaction::getValue));

참고: Modern Java in Action (라울-게이브리얼 등 지음)

profile
우당탕탕

0개의 댓글