Java Stream 정리하기(2)

gryoh·2022년 3월 13일
1

저번 포스팅에서 알아보지 못한 중간연산과 최종연산에 대해 알아보자.


중간연산(intermediate operations))

전체 요소 중에서 다음과 같은 API 를 이용해서 내가 원하는 것만 뽑아낼 수 있다

이러한 가공 단계를 중간 작업(intermediate operations)이라고 하는데, 이러한 작업은 스트림을 리턴하기 때문에 여러 작업을 이어 붙여서(chaining) 작성할 수 있다.

그럼 이제 중간연산에 해당하는 stream api에 대해 알아보자.


필요한 컬렉션을 선언

List<String> list = Arrays.asList("oh", "kim", "lee");

*Filtering
필터(filter)는 말 그대로 걸러내는 작업이다. 스트림 내 요소들을 하나씩 확인하여 걸러낸다. 인자로 받는 predicate 는 boolean 을 리턴하는 함수형 인터페이스로 평가식이 들어가게 된다.

Stream<T> filter(Predicate<? super T> predicate);

이름에 “o”가 들어간 이름을 찾는 스트림 만들기

Stream<String> stringStream = list.stream().filter(name -> name.contains("o"));
//[oh, choi]

*Mapping
맵(map)은 스트림 내 요소들을 하나씩 특정 값으로 변환해준다. 이 때 값을 변환하기 위한 람다를 인자로 받는다.

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

스트림에 들어가 있는 값이 input 이 되어서 특정 로직을 거친 후 output 이 되어 새로운 스트림에 담기게 된다. 이러한 작업을 맵핑(mapping)이라고 한다.


이름을 모두 대문자로 변환하는 스트림 생성

Stream<String> stringStream = list.stream().map(String::toUpperCase); 
//[OH, KIM, CHOI]

대문자 변경, 문자열 추가, 숫자일 경우엔 *100 등 다양한 처리 결과를 얻을 수 있다.


*mapToInt, mapToLong, mapToDouble

IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
IntStream i = list.stream().mapToInt(name -> name.length());
System.out.println(i.sum());
// 9

*Sorting()
정렬의 방법은 다른 정렬과 마찬가지로 Comparator 를 이용한다.

sorted()의 내부구조에 대해 알아보자

Stream<T> sorted();

Stream<T> sorted(Comparator<? super T> comparator);

이름을 정렬하는 스트림 생성

// 오름차순
Stream<String> streamASC = list.stream().sorted();
// [choi, kim, oh]

// 내림차순
Stream<String> streamDESC = list.stream().sorted(Comparator.reverseOrder());
// [oh, kim, choi]

sorted에 인자를 넣지 않으면 오름차순으로 정렬된다.

ComparatorreverseOrder()내림차순으로 정렬된다.

Comparatorcompare()은 두 인자를 비교해서 값을 리턴합니다.

기본적으로 Comparator 사용법과 동일하다.

문자열 길이를 기준으로 정렬해보자

// 오름차순
Stream<String> streamByLenASC = list.stream()
                                    .sorted(Comparator.comparingInt(String::length));
Stream<String> streamByLenASC2 = list.stream()
                                      .sorted((s1, s2) -> s1.length() - s2.length());
// [oh, kim, choi]

// 내림차순
Stream<String> streamByLenDESC = list.stream()
                                     .sorted((s1, s2) -> s2.length() - s1.length());
// [choi, kim, oh]

*Iterating
스트림 내 요소들 각각을 대상으로 특정 연산을 수행하는 메소드로는 peek 이 있다. peek은 그냥 확인해본다는 단어 뜻처럼 특정 결과를 반환하지 않는 함수형 인터페이스 Consumer를 인자로 받는다.

Stream<String> peekStream = list.stream().map(String::toUpperCase)
                                         .peek(str -> System.out.println(str + "님 확인"));
System.out.println(peekStream.collect(Collectors.toList()));
OH님 확인
KIM님 확인
CHOI님 확인
[OH, KIM, CHOI]

*distinct
distinct는 중복제거 역할을 한다.

Stream<T> distinct();

요소들의 중복을 제거해준다.

list = Arrays.asList("oh", "kim", "choi", "oh");
Stream<String> distinctStream = list.stream().distinct();
// [oh, kim, choi]

“oh”가 2개이므로 중복제거되어 3개만 나오는 것을 볼 수 있다.


*limit
limit()는 인자의 갯수만큼 가져와 새로운 stream을 생성한다.

Stream<T> limit(long maxSize);

list에서 2개의 요소만 가져오기

Stream<String> listLimitTwo = list.stream().limit(2);
// [oh, kim]

*skip()
skip()은 인자의 갯수만큼 제외하고 나머지 요소들로 새로운 stream을 생성한다.

Stream<T> skip(long n);

맨 앞 1개를 제외하고 나머지 요소만 가져오기

Stream<String> listSkipOne = list.stream().skip(1);
// [kim, choi]

최종연산
*Reduction
reduce()는 스트림의 요소를 줄여나가면서 연산을 수행하고 최종결과를 반환한다. 처음 두 요소를 가지고 연산한 결과를 가지고 그 다음 요소와 연산한다. 이 과정에서 스트림의 요소를 하나씩 소모하게 되며, 스트림의 모든 요소를 소모하게 되면 그 결과를 반환한다.


reduce 메소드는 총 세 가지의 파라미터를 받을 수 있다.

  • accumulator : 각 요소를 처리하는 계산 로직. 각 요소가 올 때마다 중간 결과를 생성하는 로직.
  • identity : 계산을 위한 초기값으로 스트림이 비어서 계산할 내용이 없더라도 이 값은 리턴.
  • combiner : 병렬(parallel) 스트림에서 나눠 계산한 결과를 하나로 합치는 동작하는 로직.
// 1개 (accumulator)
T reduce(T identity, BinaryOperator<T> accumulator);

// 2개 (identity)
Optional<T> reduce(BinaryOperator<T> accumulator);

// 3개 (combiner)
<U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);

*collect
스트림의 요소를 수집하는 최종 연산으로 앞서 나온 reduce()와 유사하다. collect()가 스트림의 요소를 수집하려면, 어떻게 수집할 것인가에 대한 방법이 정의되어 있어야 하는데, 이방법을 정의한 것이 바로 컬렉터(collector)이다. 컬렉터는 Collector인터페이스를 구현한 것으로, 직접 구현할 수도 있고 미리 작성된 것을 사용할 수도 있다. Collectors클래스는 미리작성된 다양한 종류의 컬렉터를 반환하는 static메서드를 가지고 있다.

<R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);
<R, A> R collect(Collector<? super T, A, R> collector);

Collectors.toList() 스트림에서 작업한 결과를 담은 리스트로 반환한다.

다음 예제에서는 map 으로 각 요소의 이름을 대문자로 가져온 후 Collectors.toList 를 이용해서 리스트로 결과를 가져온다.

System.out.println(list.stream().map(String::toUpperCase).collect(Collectors.toList()));

Collectors.joining() 스트림에서 작업한 결과를 하나의 스트링으로 이어 붙일 수 있다.

다음 예제는 이름을 ‘/’로 이어 붙인 예제이다.

System.out.println(list.stream().collect(Collectors.joining("/")));

*flatMap()

*forEach


*count(), min(), max(), sum(), average()


*groupingBy

0개의 댓글