상세 컨텐츠

본문 제목

[Java] 10. Stream 마무리-1

프로그래밍 언어/Java(자바)

by Guroo 2023. 9. 22. 17:32

본문

[Java] 9. 다양한 Stream의 생성

 

[Java] 9. 다양한 Stream의 생성

[Java] 8. Stream의 개념과 사용 방법-1 [Java] 8. Stream의 개념과 사용 방법-1 [Java] 7. 메소드 참조 (::) [Java] 7. 메소드 참조 (::) 6. Map에 정의된 추상 메소드와 default 메소드 6. Map에 정의된 추상 메소드와 de

sesoc.tistory.com

 

이제 스트림을 마무리하도록 하자.

 

이전 스토리에 다양한 스트림의 생성과 사용법에 대해 간략히 살펴보았다.

 

먼저, 스트림의 중간연산 중 다루지 않은 메소드를 살펴보고, 예제를 통해 그 결과물을 확인해 볼 것이다. 스트림의 연산에 대한 내용은 매우 다양하므로, 지금까지 다루지 못한 내용은 앞으로 차차 살펴보도록 할 예정이다.

 

10-1. 중간연산 메소드 2

아래의 표는 지난 기사에서 살펴본 중간 메소드를 포함하여 몇 개의 중간연산 메소드가 있다. 

(회색처리된 메소드는 이미 다룬 메소드임)

 

(이전 글 참고 : https://sesoc.tistory.com/196)

반환타입 메소드 설명
Stream<T> distinct() 데이터의 중복을 제거
Stream<T> filter(Predicate<T> p) Predicate Lambda식을 전달하여 그 조건에 맞는 데이터를 필터링
Stream<T> limit(long size) 스트림을 전달인자의 값에 맞게 잘라낸다
Stream<T> sorted() 스트림 데이터를 정렬한다.
Stream<T> sorted(Comparator<T> f) 스트림 데이터를 정렬한다.
Stream<T> skip(long n) 스트림의 데이터를 건너뛴다.
Stream<T> peek(Consumer<T> c) 스트림의 데이터를 peek한다.
Stream<R> map(Function<T, R> f) 메소드 내부에 전달된 람다식이 적용되어 변경된 스트림 데이터를 반환한다.
     
DoubleStream mapToDouble(ToDoubleFunction<T> f) map 메소드와 유사하며 반환된 스트림의 타입이 DoubleStream이다.
IntStream mapToInt(ToIntFunction<T> f) 위와 동일(IntStream 반환)
LongStream mapToLong(ToLongFunction<T> f) 위와 동일(LongStream 반환)
Optional<T reduce(BinaryOperator<T> accumulator) 스트림 데이터의 각 요소들을 하나의 데이터로 만드는 작업을 수행한다.

 

10-2. peek()

 

스트림에서 제공하는 중간연산 메소드로 peek() 메소드가 있다. 이 메소드는 스트림 데이터의 흐름에서 특정 요소들을 확인하기 위한 용도이다.

 

Java API 문서에는 주로 파이프라인의 특정 지점을 지나 흐르는 요소를 확인하고 디버깅을 지원하기 위한 메소드라고 설명되어 있다.

 

아래의 peek() Method Signature를 보면 peek() 메소드가 Consumer 타입의 Lambda 식을 전달받음을 알 수 있는데, 이미 사용해 보았던 forEach() 메소드의 전달인자도 Consumer 타입을 전달받아 두 메소드를 동일하다고 착각할 수 있다.

 

그러나 forEach() 메소드는 스트림의 최종 연산이고, peek()는 중간연산이므로 peek()와 forEach()는 같지 않다.

Stream<T> peek(Consumer<? super T> action)

void forEach(Consumer<? super T> action)

 

 

예제를 통해 확인해보자

 

PeekMethodTest1.java

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class PeekMethodTest1 {
	public static void main(String[] args) {
        List<String> list = new ArrayList<String>();

        // 길이가 3을 초과하는 문자열을 추출하여 List에 담는다.
        Stream.of("one", "two", "three", "four")
            .filter(e -> e.length() > 3)
            .peek(e -> list.add(e))   // filter()로 걸러진(?) 데이터를 list에 담는다.
            .collect(Collectors.toList());

        // 길이가 3을 초과하는 문자열을 추출하고, 추출된 문자열을 대문자로 변경한 후
        // List에 담는다.
        Stream.of("five", "six", "seven", "eight")
            .filter(e -> e.length() > 3)  // filter()로 데이터를 거른 후
            .map(e -> e.toUpperCase())    // 대문자로 변환해서
            .peek(e -> list.add(e))       // List에 담는다.
            .collect(Collectors.toList());

        // 리스트에 수집된 데이터를 출력한다.
        System.out.println(list);
    }
}

 

** 결과

 

[three, four, FIVE, SEVEN, EIGHT]

 

다음 코드를 살펴보자.

 

PeekMethodTest2.java

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class PeekMethodTest2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();

        // 첫 번째 (중간연산에서 문장이 끝남)
        System.out.println("첫 번째 결과");
        Stream.of("one", "two", "three", "four")
            .filter(e -> e.length() > 3)
            .peek(System.out::println);

        // 두 번째
        System.out.println("두 번째 결과");
        Stream.of("one", "two", "three", "four")
            .filter(e -> e.length() > 3)
            .forEach(System.out::println);

        // 세 번째
        System.out.println("세 번째 결과");
        Stream.of("one", "two", "three", "four")
            .filter(e -> e.length() > 3)
            .peek(System.out::println)
            .collect(Collectors.toList());
    }
}

 

** 결과

== 첫 번째 결과

== 두 번째 결과
three
four

== 세 번째 결과
three
four

 

두 번째와 세 번째 출력의 결과는 같은데, 첫 번째의 결과는 제대로 출력되지 않았다.

만약 peekforEach로 변경하거나 peek() 메소드 뒤에 collect()를 두면 면 결과를 볼 수 있다.

 

??

 

이유는 peek()중간연산이기 때문이다.

그러므로 peek(System.out::println) 을 사용했다고 해서 출력되는 것이 아니라 반드시 최종연산으로 마무리 작업을 해야 결과를 출력할 수 있다는 것을 잊어서는 안된다.

 

peek()를 이용해서 출력할 것을 확인하고 최종 연산으로 마무리해야 실제 출력이 이뤄진다고 이해하면 되겠다.

 

이제 문제를 풀어보자.

 

아래와 같은 스트림이 주어졌다.

스트림에 "밥"이라는 단어가 포함된 문자열만 추출한 후 문자열의 마지막에 있는 "밥"을 삭제, 오름차순으로 정렬한 후 이렇게 추출된 단어를 List에 담아 출력하는 것이 문제다

 

Stream<String> stream = List.of("순대국밥", "설렁탕", "양푼비빔밥", "돌솥비빔밥", "비빔국수", "부대찌게", "달걀볶음밥").stream();

 

PeekMethodTest3.java

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class PeekMethodTest3 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();

		Stream<String> stream = List.of("순대국밥", "설렁탕", "양푼비빔밥", "돌솥비빔밥", "비빔국수", "부대찌게", "달걀볶음밥").stream();

        stream
            .filter(x -> x.contains("밥"))
            .sorted((x, y) -> x.compareTo(y))
            .peek(x -> list.add(x.substring(0, x.length()-1)))
            .collect(Collectors.toList());
        System.out.println(list);
    }
}

 

앞서 보았던 코드와 유사하기 때문에 잘 해결했을 것이라 생각한다.

 

10-3. skip()

이제 살펴볼 skip() 메소드는 limit()와 유사한 메소드라고 생각하면 쉽다.

앞서 살펴본 limit()스트림의 앞에서부터 몇 개의 데이터를 추출하는 메소드이고skip()중간에서부터 뒤까지 추출하는 메소드이다.

 

SkipTest.java

import java.util.stream.IntStream;

public class SkipTest {
    public static void main(String[] args) {
        // 1~ 100사이의 자연수 중 5의 배수를 찾아 앞 10개만 출력
        IntStream.range(1, 100)
            .filter(x -> x%5 == 0? true: false)
            .limit(10)
            .forEach(x -> System.out.print(x + " "));

        System.out.println();

        // 1~ 100사이의 자연수 중 5의 배수를 찾아 앞 10개 이후부터 출력
        IntStream.range(1, 100)
            .filter(x -> x%5 == 0? true: false)
            .skip(10)
            .forEach(x -> System.out.print(x + " "));
    }
}

 

** 결과

5 10 15 20 25 30 35 40 45 50
55 60 65 70 75 80 85 90 95

 

글이 길어지니 map() 메소드는 다음 글에서 살펴보자.

 

 


이전 글: [Java] 9. 다양한 Stream의 생성

다음 글: [Java] 11. Stream 마무리-2

 

관련글 더보기

댓글 영역