업데이트:

이전 포스트에서 Stream의 기본적인 내용에 대해 알아봤습니다.

이번에는 자주 사용하는 Stream의 메소드에 대해서 알아보겠습니다.

💡참고로 Stream에서 사용하는 메소드는 LambdaMethod Reference, Functional Interface를 사용합니다. 이 중에서도 주로 Lambda를 사용합니다. 이는 다른 포스팅에서 함께 다뤄보도록 하겠습니다~

중간 연산 메소드 (Intermediate Operation Method)


이전 포스트에서 Stream은 총 2가지 메소드로 나눠지는 것을 알려드렸었는데요, 먼저 자주 사용하는 중간 메소드에는 무엇이 있는지 알아보겠습니다.

filter()

filter()는 조건에 맞는 요소를 걸러주는 역할을 합니다.

중간 연산 메소드인 만큼 Stream을 반환하기 때문에 이후에 다른 중간 연산, 최종 연산이 가능합니다.

다음은 5명의 이름에서 5글자 이상인 사람의 이름을 필터링하는 코드입니다.

@Test
void filter() {
    List<String> players = List.of("jordan", "kobe", "iverson", "wade", "curry");
    players.stream()
        .filter(name -> name.length() > 5)
        .forEach(System.out::println);
}

filter

결과는 위와 같이 jordan, iverson, curry가 출력된 것을 확인할 수 있습니다.

map()

map()의 생김새는 다음과 같습니다.

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

map()은 요소의 기존 타입을 다른 타입으로 변환 시켜주는 메소드입니다.

물론 원래 타입을 그대로 유지해도 됩니다. 타입을 그대로 유지한다?

즉, 타입은 유지하되 원래 요소의 무언가 작업을 진행한다는 뜻이겠죠?

다음은 5명의 이름 글자 수로 변환하여 출력하는 코드입니다.

@Test
void map() {
    List<String> players = List.of("jordan", "kobe", "iverson", "wade", "curry");
    players.stream()
        .map(name -> name.length())
        .forEach(System.out::println);
		
		players.stream()
        .map(name -> name + name.length())
        .forEach(System.out::println);
}

map1

다음은 이름과 글자 수를 합쳐 출력하는 코드입니다.

@Test
void map2() {
    List<String> players = List.of("jordan", "kobe", "iverson", "wade", "curry");
    players.stream()
        .map(name -> name + name.length())
        .forEach(System.out::println);
}

map2

flatMap()

제가 Stream을 공부하면서 제일 헷갈렸던 메소드입니다 🤣

“요소를 단일 Stream으로 변환해준다.”라는 어려운 설명이 있지만 flatMap()은 결국 2차원 배열, 혹은 2중 컬렉션을 반복하여 작업하는 행위를 한다고 생각해주시면 쉽게 이해가 되실겁니다.

flatMap()의 생김새는 다음과 같습니다.

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

위의 map()과 차이점을 확인해보면, 2번째 인자로 Stream의 하위 타입이 와야됨을 확인 할 수 있습니다.

다음은 2중 컬렉션을 하나의 컬렉션으로 취합하는 코드입니다.

@Test
void flatMap() {

    List<List<String>> allPlayers = new ArrayList<>();
    List<String> players1 = List.of("jordan", "kobe");
    List<String> players2 = List.of("iverson", "wade");
    List<String> players3 = List.of("curry", "lebron");

    allPlayers.add(players1);
    allPlayers.add(players2);
    allPlayers.add(players3);

    List<String> collect = allPlayers.stream()
        .flatMap(players -> players.stream())
        .collect(Collectors.toList());

    collect.stream()
        .forEach(System.out::println);

}

flatMap

이 밖에도 여러 과목의 시험을 친 학생들의 총 평균을 구하는 좋은 예제가 있는 포스트가 있어 소개해드리겠습니다.

[Java] Stream API의 활용 및 사용법 - 고급 (4/5)

💡map()flatMap()은 각각 타입을 정해놓은 mapToInt(), flatMapToInt() 같은 메소드가 있습니다. 이를 활용하면 더 확실하게 타입 변환을 할 수 있습니다.

peek()

peek()은 최종 연산 메소드인 forEach() 같이 반환하지 않는(void)인 작업을 하는 메소드입니다.

대표적인 예로 System.out.println()이 있습니다.

주로 다른 여러 작업 사이의 요소 값을 확인하기 위해 사용합니다.

❗주의할 점은 forEach()와 달리 중간 연산이기 때문에 최종 연산이 없으면 실행되지 않습니다.

@Test
void peek() {
    List<String> players = List.of("jordan", "kobe", "iverson", "wade", "curry");
    players.stream()
        .peek(System.out::println);
}

위와 같이 최종 연산을 하지 않은 상태에서 실행하면 아무런 값도 출력되지 않습니다.

peek1

이번엔 5명 중 이름의 길이가 5글자 이상인 사람들을 peek()으로 확인하고 필터링된 사람의 수를 구하는 코드를 작성해봤습니다.

@Test
void peek() {
    List<String> players = List.of("jordan", "kobe", "iverson", "wade", "curry");
    long count = players.stream()
        .filter(name -> name.length() >= 5)
        .peek(System.out::println)
        .count();

    System.out.println("count = " + count);
}

peek2

결과를 보면 peek()이 제대로 사용된 것을 확인할 수 있습니다.

sorted()

sorted()는 이름에서 바로 알 수 있듯이 요소를 정렬해주는 메소드입니다.

sorted()에서 제공하는 건 2개가 있습니다.

하나는 인자가 없는 메소드드와 Comparator를 인자로 받는 sotred()가 있습니다.

먼저 sorted()의 설명을 보면

sorted

natural order, 즉 자연순에 따라 정렬된다고 나와있습니다.

보통 우리가 사용하는 java.util.*ArraysCollectionsnatural orderAscending(오름차순)입니다.

문자열 정렬

@Test
void string_sorted() {
    List<String> players = List.of("jordan", "kobe", "iverson", "wade", "curry");
    players.stream().sorted()
        .forEach(System.out::println);
}

string_sorted

숫자 정렬

@Test
void integer_sorted() {
    List<Integer> numbers = List.of(4, 5, 1, 3, 2);
    numbers.stream().sorted()
        .forEach(System.out::println);
}

integer_sorted

결과에서 확인할 수 있듯이 오름차순 정렬된 것을 확인할 수 있습니다.

다음은 Comparator를 인자로 받는 Stream<T> sorted(Comparator<? super T> comparator)입니다.

정렬 방법을 Comparator를 정의하여 넘겨주는 것인데, 간단하게 Comparator.reverseOrder()를 사용하여 역순으로 정렬해보도록 하겠습니다.

@Test
void reverse_sorted() {
    List<String> players = List.of("jordan", "kobe", "iverson", "wade", "curry");
    players.stream()
        .sorted(Comparator.reverseOrder())
        .forEach(System.out::println);
}

reverse_sorted

아까와는 반대로 정렬된 것을 확인할 수 있습니다.

distinct()

distinct() 역시 이름에서 알수 있듯이 중복제거 입니다.

같은 이름을 2번 사용했을때, 중복을 제거하고 결과를 출력하는 것을 확인할 수 있습니다.

@Test
void distinct() {
    List<String> players = List.of("jordan", "kobe", "iverson", "wade", "curry", "jordan", "kobe", "iverson", "wade", "curry");
    players.stream()
        .distinct()
        .forEach(System.out::println);
}

distinct

limit()

limit()는 결과 중 몇개만 출력하겠다는 메소드입니다.

@Test
void limit() {
    List<String> players = List.of("jordan", "kobe", "iverson", "wade", "curry");
    players.stream()
        .limit(3)
        .forEach(System.out::println);
}

limit

결과를 보면 무작위로 3개를 출력하는 것이 아니라 최종 결과물에서 3개까지만 출력하는 것입니다.

skip()

skip()limit()와 달리 말 그대로 몇개를 건너뛰고 최종 결과물을 출력하겠다는 뜻입니다.

바로 코드를 보겠습니다.

@Test
void skip() {
    List<String> players = List.of("jordan", "kobe", "iverson", "wade", "curry");
    players.stream()
        .skip(3)
        .forEach(System.out::println);
}

아까 limit() 코드와 똑같지만 limit()skip()으로 바꼈습니다.

결과물은?

skip

이번엔 limit()과 달리 3개를 건너뛰고 남은 2개 결과만 출력되었습니다.

마무리


오늘은 Java Stream에서 자주 사용하는 메소드 중에서도 중간 연산 메소드를 확인해봤습니다.

원래는 최종 연산 메소드까지 한번에 하려했는데 생각보다 양이 꽤 돼네요. 😢

한 포스트에서 다뤄보기엔 보기에도 지루하실꺼고 저도 힘들고해서 포스트를 나누게 되었습니다.

다음 포스트에서 최종 연산 메소드를 다뤄보도록 하겠습니다

오늘도 제 포스트를 봐주셔서 감사합니다.😄

댓글남기기