티스토리 뷰

개념

  • 스트림의 요소를 선택하거나 스킵하는 다양한 방법에 대해 살펴보자.
  • Predicate를 이용하는 방법, 스트림의 처음 몇 개의 요소를 무시하는 방법, 특정 크기로 스트림을 줄이는 방법 등 다양한 방법을 이용해 효율적으로 이런 작업을 수행할 수 있다.
  • 이러한 작업을 슬라이싱이라고 한다.

takeWhile 메서드

  • Java 9에 추가된 메서드

  • Predicate<T>를 파라미터로 받아서 해당 Predicatefalse를 반환할 때까지의 요소를 취하는 메서드다.

  • 얼핏보면 filter 메서드와 유사해보이지만, filter 메서드가 전체 스트림에 대한 Predicate를 판단하는 반면 takeWhilePredicatefalse를 반환하는 순간 나머지 요소를 전부 버린다.

    • 아주 많은 요소를 가진 스트림에서는 이런 부분이 큰 차이를 유발할 수 있다.
    • 특히, 정렬되어 있는 요소에 대해서 큰 장점을 가질 수 있다.
  • 무한 스트림을 포함한 모든 스트림에 Predicate를 적용해 스트림을 슬라이스할 수 있다.

dropWhile 메서드

  • Java 9에 추가된 메서드
  • Predicate<T>를 파라미터로 받아서 해당 Predicatefalse를 반환할 때까지의 요소를 모두 버리고 나머지 요소를 반환하는 메서드다.
  • takeWhile과 정반대로 작업을 수행한다.
  • Predicatefalse가 되는 지점에서 작업을 중단하고 남은 모든 요소를 반환한다.

limit 메서드

  • 주어진 값 이하의 크기를 갖는 새로운 스트림을 반환하는 메서드
  • long을 파라미터로 받아서 해당 숫자 이하의 요소를 반환
  • 소스가 정렬되어 있지 않았다면 limit의 결과도 정렬되지 않은 상태로 반환된다.

skip 메서드

  • 처음 n개 요소를 제외한 스트림을 반환하는 메서드
  • long을 파라미터로 받아서 해당 숫자 이하의 요소를 버리고 나머지를 반환
  • n개 이하의 요소를 포함하는 스트림에 skip(n)을 호출하면 빈 스트림이 반환된다.

예제

  • 아래 예제에서 사용되는 내용들은 다양한 상황을 연출하기 위해서 임의로 만들어진 내용이므로 특정 속성에 대해서 왜 저 타입이 사용되었는지 의문을 가지지 말자!

  • 예제 코드에서 사용되고 있는 스펙

    1. Java 15 preview (record라는 새로운 클래스 개념을 사용하기 위해서 해당 프리뷰 버전을 사용. 하위 버전의 경우는 일반 클래스를 생성한 후 getter를 만들고 사용하면 됨) - 참고
    2. JUnit 5
    3. Gradle 6.7
  • 소스 코드

기본 데이터 생성

public record Song(int no, String title, String artist, int likeCount, LocalDate releasedDate) {
}
private final SlicingUsage slicingUsage = new SlicingUsage();
private final List<Song> songs = List.of(
        new Song(1, "밤편지", "아이유", 393_424, LocalDate.of(2017, 3,24)),
        new Song(2, "Love poem", "아이유", 280_586, LocalDate.of(2019, 11,1)),
        new Song(3, "Blueming", "아이유", 280_323, LocalDate.of(2019, 11,18)),
        new Song(4, "삐삐", "아이유", 264_664, LocalDate.of(2018, 10,10)),
        new Song(5, "가을 아침", "아이유", 257_136, LocalDate.of(2017, 9,22)),
        new Song(6, "팔레트", "아이유", 244_865, LocalDate.of(2017, 4,21)));
  • 주어진 데이터가 likeCount좋아요 순으로 정렬되어 있다는 점에 집중하며 예제를 살펴보자.

특정 조건을 만족하는 노래들 조회

  • 특정 조건을 만족하지 않는 데이터가 나타날 때까지의 요소를 반환하는 메서드를 만들어보자. (takeWhile 이용)
public List<Song> getSongsWithTakeWhile(List<Song> songs, Predicate<Song> predicate) {
    return songs.stream()
            .takeWhile(predicate)
            .collect(Collectors.toList());
}
  • 좋아요가 28만개 이상인 노래들을 조회해보자.
@Test
@DisplayName("좋아요가 28만개 이상인 노래들 조회")
void getSongsWithTakeWhileTest() {
    final var result = slicingUsage.getSongsWithTakeWhile(songs, i -> i.likeCount() >= 280_000);
    result.forEach(System.out::println);
    assertEquals(3, result.size());
    assertEquals("밤편지", result.get(0).title());
    assertEquals("Love poem", result.get(1).title());
    assertEquals("Blueming", result.get(2).title());
}
  • 콘솔에 다음과 같은 실행 결과와 함께 테스트가 성공한다.

특정 조건을 만족하는 노래를 제외한 나머지 노래들 조회

  • 특정 조건을 만족하지 않는 데이터가 나타날 때까지 요소를 버리고 나머지를 반환하는 메서드를 만들어보자. (dropWhile 이용)
public List<Song> getSongsWithDropWhile(List<Song> songs, Predicate<Song> predicate) {
    return songs.stream()
            .dropWhile(predicate)
            .collect(Collectors.toList());
}
  • 좋아요가 28만개보다 작은 요소들을 조회해보자.
@Test
@DisplayName("좋아요가 28만개 이상인 노래를 제외한 나머지 노래들 조회")
void getSongsWithDropWhileTest() {
    final var result = slicingUsage.getSongsWithDropWhile(songs, i -> i.likeCount() >= 280_000);
    result.forEach(System.out::println);
    assertEquals(3, result.size());
    assertEquals("삐삐", result.get(0).title());
    assertEquals("가을 아침", result.get(1).title());
    assertEquals("팔레트", result.get(2).title());
}
  • 콘솔에 다음과 같은 실행 결과와 함께 테스트가 성공한다.

n개까지의 노래들 조회

  • 스트림에서 n개의 요소를 반환하는 메서드를 만들어보자. (limit 이용)
public List<Song> getSongsWithLimit(List<Song> songs, int size) {
    return songs.stream()
            .limit(size)
            .collect(Collectors.toList());
}
  • 상위 2개의 노래들을 조회해보자.
@Test
@DisplayName("상위 2개 노래들 조회")
void getSongsWithLimitTest() {
    final var result = slicingUsage.getSongsWithLimit(songs, 2);
    result.forEach(System.out::println);
    assertEquals(2, result.size());
    assertEquals("밤편지", result.get(0).title());
    assertEquals("Love poem", result.get(1).title());
}
  • 콘솔에 다음과 같은 실행 결과와 함께 테스트가 성공한다.

n개를 제외한 나머지 노래들 조회

  • 스트림에서 n개의 요소를 버리고 나머지를 반환하는 메서드를 만들어보자. (skip 이용)
public List<Song> getSongsWithSkip(List<Song> songs, int offset) {
    return songs.stream()
            .skip(offset)
            .collect(Collectors.toList());
}
  • 상위 4개를 노래를 건너띄고 나머지 노래들을 조회해보자.
@Test
@DisplayName("상위 4개를 제외한 나머지 노래들 조회")
void getSongsWithSkipTest() {
    final var result = slicingUsage.getSongsWithSkip(songs, 4);
    result.forEach(System.out::println);
    assertEquals(2, result.size());
    assertEquals("가을 아침", result.get(0).title());
    assertEquals("팔레트", result.get(1).title());
}
  • 콘솔에 다음과 같은 실행 결과와 함께 테스트가 성공한다.

이상 Stream API에서 사용하는 중간 연산 중 요소의 건너띄거나 특정 크기로 줄이는 등 슬라이싱 메서드들에 대해 살펴보았다.

댓글