본문 바로가기
독서/모던 자바 인 액션

[자바] 스트림(Stream) 활용

by kriorsen 2023. 10. 5.

7.1 스트림 슬라이싱

1. takeWhile의 활용

takeWhile 메서드는 Stream의 각 요소 중 조건을 만족하지 않는 첫 번째 요소를 만나면 현재까지의 요소를 반환합니다.

정렬된 경우

import java.util.List;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> result = numbers.stream()
                                    .takeWhile(n -> n < 6)
                                    .toList();

        System.out.println(result); // [1, 2, 3, 4, 5]
    }
}

정렬되지 않은 경우

import java.util.List;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(3, 1, 7, 5, 2, 6, 4, 8);

        List<Integer> result = numbers.stream()
                                    .takeWhile(n -> n < 6) 
                                    .toList();

        System.out.println(result); // [3, 1, 5, 2]
    }
}

 

2. dropWhile의 활용

dropWhile 메서드는 처음으로 거짓이 되는 지점까지 발견된 요소들을 버리는 역할로 takeWhile의 정반대의 기능입니다.

import java.util.List;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 조건을 만족하지 않는 요소들을 제외한 후 나머지 요소들을 포함하는 스트림 생성
        List<Integer> result = numbers.stream()
                                    .dropWhile(n -> n < 6) // 6 이상인 요소들을 제외한 나머지 요소들을 포함하는 스트림 생성
                                    .toList();

        System.out.println(result); // [6, 7, 8, 9, 10]
    }
}

 

3. skip과 limit의 활용

skip 메서드는 스트림에서 처음 몇 개의 요소를 건너뛴 다음에 나오는 요소들을 반환하고, limit 메서드는 처음 몇 개의 요소만을 반환하는 역할을 합니다.

import java.util.List;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 처음 2개의 요소를 건너뛰고 다음 3개의 요소만을 반환하는 스트림 생성
        List<Integer> result = numbers.stream()
                                    .skip(2)  // 처음 2개의 요소를 건너뜀
                                    .limit(3) // 다음 3개의 요소만을 반환
                                    .toList(); 

        System.out.println(result); // [3, 4, 5]
    }
}

7.2 매핑

1. map의 활용

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

map은 스트림의 각 요소에 대해 특정 함수를 적용해 새로운 값으로 반환해 주는 역할을 합니다.

import java.util.List;
import java.util.Arrays;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 각 요소를 제곱하여 새로운 스트림 생성
        List<Integer> squares = numbers.stream()
                                      .map(n -> n * n)
                                      .collect(Collectors.toList());
                                      
        System.out.println(squares); // [1, 4, 9, 16, 25]
    }
}

 

2. flatMap의 활용

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

flatMap은 스트림의 각 요소에 대해 함수를 적용하고 학 함수가 반환한 스트림을 하나의 스트림으로 병합하는 기능을 제공합니다. 즉, 중첩된 구조를 펼쳐서 평면화된 요소를 하나의 스트림으로 합쳐 줍니다. (List<List<Integer>> -> List<Integer>)

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

class Person {
    private String name;
    private List<String> hobbies;

    public Person(String name, List<String> hobbies) {
        this.name = name;
        this.hobbies = hobbies;
    }

    public List<String> getHobbies() {
        return hobbies;
    }
}

public class Main {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", Arrays.asList("Reading", "Traveling")),
            new Person("Bob", Arrays.asList("Cooking", "Hiking"))
        );

        // 각 사람의 취미들을 평면화된 리스트로 변환
        List<String> hobbies = people.stream()
                                    .flatMap(person -> person.getHobbies().stream()) // 각 사람의 취미 리스트를 개별 스트림으로 변환하고 병합
                                    .toList(); 

        System.out.println(hobbies); // [Reading, Traveling, Cooking, Hiking]
    }
}

7.3 무한 스트림 만들기

1. iterate 활용

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)

// UnaryOperator
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

    /**
     * Returns a unary operator that always returns its input argument.
     *
     * @param <T> the type of the input and output of the operator
     * @return a unary operator that always returns its input argument
     */
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

iterate 메서드는 seed라는 초기값을 받아서 UnaryOperator를 구현한 로직으로 seed값을 가공해 무한 스트림을 만들 수 있습니다.(Predicate로 조건 적용도 가능)

public static void main(String[] args) {
        Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 1)
                .map(integer -> {
                    System.out.println(integer);
                    return integer;
                })
                .limit(10);
                
        System.out.println("Hello");
        infiniteStream.forEach(System.out::println);
    }
//        실행 결과
//        Hello
//        0
//        0
//        1
//        1
//        2
//        2
//        ...
//        8
//        9
//        9

Stream의 연산은 lazy evaluation으로 인해서, Stream을 반환하는 중간 연산은 forEach, count, collect, average 등의 종료 연산을 만나기 전까지 호출이 지연된다는 특징이 있으며, 이를 위 코드의 실행 결과를 통해 확인할 수 있습니다.

 

2. generate의 활용

generate 메서드는 Supplier를 받아서 무한 스트림을 생성합니다. iterate 메서드와 동일하게 limit과 함께 조합하여 사용할 수 있습니다.

import java.util.Random;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        // 난수 생성기를 이용하여 무한한 난수 스트림 생성
        Stream<Integer> randomNumbers = Stream.generate(() -> new Random().nextInt(100));

        // 처음 5개의 난수 출력 (무한 스트림이지만, limit를 사용하여 제한)
        randomNumbers.limit(5)
                     .forEach(System.out::println);
    }
}