본문 바로가기
프로그래밍/JAVA 내용정리

[ModernJavaInAction] 5장 스트림의 활용

by 노잼인간이라불립니다 2022. 9. 12.

이 글은 MordernJavaInAction을 읽고 정리한 글 입니다.

 

안녕하세요. 저번 글에서는 스트림에 대해서 알아 보았는데요.

 

이번글에서는 스트림의 활용에 대해 알아보고자 합니다.

 

스트림에 대해서 궁금하신 분들은 아래 링크에서 읽어보시길 바랄게요~

 

https://jojoplot2.tistory.com/entry/JavaInAction-%EC%8A%A4%ED%8A%B8%EB%A6%BCStream

 

[ModernJavaInAction] 스트림(Stream)

이 글은 모던 자바 인 액션이라는 책을 읽고 스스로 내용을 정리하여 작성 한 글입니다. 안녕하세요. 오늘은 스트림에 대해서 알아보고자 합니다. 스트림은 JAVA 8 부터 API에 새로 추가된 기능입

jojoplot2.tistory.com

 

모든 실습 코드는 아래 주소에 있습니다.

https://github.com/jojojojocho/mordernjavainaction

 

GitHub - jojojojocho/mordernjavainaction: 모던자바인액션 연습코드

모던자바인액션 연습코드. Contribute to jojojojocho/mordernjavainaction development by creating an account on GitHub.

github.com

 

이번 시간에는 스트림의 활용에 대해서 알아보고자 합니다.

 

스트림은 정~~말 정~말 다양한 기능을 제공합니다.

 

자, 이제 살펴보도록 하죠.

 


 

1. 필터링

Predicate를 이용한 필터링

 

Filter()

 

스트림에서는 Predicate(boolean타입을 반환하는 함수)를 paremeter로 받아

Predicate와 일치하는 요소들 만을 스트림으로 반환하여 필터링 할 수 있습니다.

 

@DisplayName("프레디 케이트로 필터링")
@Test
public void usePredicateFilteringMethod() {
    //when
    //채식인 것들만 필터링 해서 리스트로 collect
    List<Dish> vegitarianMenu = menu.stream().filter(Dish::isVegetarian)
            .collect(Collectors.toList());

    //then
    //vegitarianMenu의 각 요소들이 채식인지 검증
    vegitarianMenu.stream().forEach(dish -> assertThat(dish.isVegetarian()).isEqualTo(true));
}

 

Unique한 요소로 필터링

 

distinct()

 

distinct 메서드는 unique한 요소로 이루어진 스트림을 반환하여 필터링 할 수 있습니다.

 

** unique 여부는 스트림에서 만든 객체의 hashcode와 equals를 이용한 비교로 결정됩니다!

 

**hashcode : 객체의 주소값을 변환한 객체 고유의 정수값.

**equals : 동등비교(논리적으로 동등한 것인가를 비교).

**논리적동등 : 둘의 참조 값이 다르더라도 내부 value가 같으면 true.

 

@DisplayName("고유 요소 필터링")
@Test
public void useUniqueElementFilteringMethod() {
    //given
    List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 2, 2, 1, 4);

    //when
    //짝수일 경우에만 필터링 후 중복제거하여 리스트로 만듬.
    List<Integer> uniqueEvenNumberList = numbers.stream()
            .filter(number -> number % 2 == 0)
            .distinct()
            .collect(Collectors.toList());

    //then
    //중복제거 완료 후에는 2,4가 있을 것이므로 리스트 사이즈가 2 일 것이다.
    assertThat(uniqueEvenNumberList.size()).isEqualTo(2);
}

2. 슬라이싱

2.1 Predicate를 이용한 슬라이싱

 

스트림에서는 Predicate를 이용한 슬라이싱을 방법으로

TAKEWHILE과 DROPWHILE 두 가지 메서드를 제공합니다.

 

두 메서드는 정렬이 되어 있을 경우에 사용해야 합니다.

두 메서드는 Predicate가 거짓이 되면 쇼트서킷이 발생합니다.

 

takeWhile()

takeWhile 메서드는  Predicate가 true인 것을 take하여 스트림으로 반환합니다.

 

@DisplayName("takeWhile을 사용 한 슬라이싱") //takeWhile은 정렬된 상태에서만 사용이 가능하다.
@Test
public void useTakeWhileSlicingMeThod() {

    //when
    //filter를 이용한 필터링 코드
    List<Dish> filteredMenu = specialMenu.stream()
            .filter(dish -> dish.getCalories() < 320)
            .collect(Collectors.toList());

    //takeWhile을 이용한 슬라이싱 코드
    List<Dish> slicedMenu = specialMenu.stream()
            .takeWhile(dish -> dish.getCalories() < 320)
            .collect(Collectors.toList());

    //then
    // 각 요소가 320이하 칼로리인지 검증.
    slicedMenu.stream().forEach(dish -> assertThat(dish.getCalories() < 320));
}

 

dropWhile()

dropWhile takeWhile 과 반대입니다.

dropWhile 메서드는 Predicate가 true인 것을 drop하여 나머지를 스트림으로 반환합니다.

 

@DisplayName("dropWhile을 사용 한 슬라이싱") // dropWhile은 정렬된 상태의 스트림에서만 사용이 가능하다.
@Test
public void useDropWhileSlicingMeThod() {

    //when
    //filter를 이용한 필터링 코드
    List<Dish> filteredMenu = specialMenu.stream()
            .filter(dish -> dish.getCalories() < 320)
            .collect(Collectors.toList());

    //dropWhile을 이용한 슬라이싱 코드
    List<Dish> slicedMenu = specialMenu.stream()
            .dropWhile(dish -> dish.getCalories() < 320)
            .collect(Collectors.toList());

    //then
    // 각 요소가 320이상 칼로리인지 검증.
    slicedMenu.stream().forEach(d -> assertThat(d.getCalories() > 320));
}

 

 

2.2 스트림 축소

 

limit()

 

limit(n)는 주어진 값 n 이하의 크기를 갖는 스트림을 반환합니다.

 

    @DisplayName("스트림 축소")
    @Test
    public void useLimit() {

        int n = 10; // 조건식에 부합 하는 것을 몇 개 반환할 것인가?

        //when
        //칼로리가 300초과 인 요리들을 N개 가져와서 리스트로 반환
        List<Dish> dishes = specialMenu.stream()
                .filter(dish -> dish.getCalories() >= 300)
                .limit(n)
                .collect(Collectors.toList());

        //then
        //리스트의 각 요소들이 300칼로리 이상인지 확인
        dishes.stream().forEach(dish -> assertThat(dish.getCalories() >= 300));
        dishes.stream().forEach(dish -> System.out.println(dish.getName()));

        //리스트의 사이즈가 N인지 확인.
//        Assertions.assertThat(dishes.stream().count()).isEqualTo(N);
    }

 

2.3 요소 건너뛰기

 

skip()

 

skip(n)은 처음 n개를 제외한 스트림을 반환합니다.

 

@DisplayName("요소 건너뛰기")
@Test
public void useSkip() {
    long n = 2;

    //when
    List<Dish> dishes = menu.stream().filter(dish -> dish.getCalories() > 300)
            .skip(n)                        //n개 만큼 건너뜀.
            .collect(Collectors.toList());

    //then
    assertThat(menu.stream()
            .filter(dish -> dish.getCalories() > 300)
            .count()).isEqualTo(dishes.size() + n);

    dishes.stream().forEach(dish -> assertThat(dish.getCalories() > 300));
    dishes.stream().forEach(dish -> System.out.println(dish.getName()));
}

3. 매핑

 

3.1 스트림의 각 요소에 함수 적용하기

 

map()

 

map 메서드는 파라미터로 함수(T -> R)를 받는 메서드입니다.

파라미터로 받은 함수는 각 요소에 적용되며, 요소들은 새로운 요소로 매핑이 됩니다. 

 

@DisplayName("스트림으로 각 요소에 함수 적용 1")
@Test
public void useMapMethodForDishToDishName() {

    //when
    List<String> dishNames = menu.stream()
            .map(dish -> dish.getName())
            .collect(Collectors.toList());

    //then
    for (int i = 0; i < menu.size(); i++) {
        assertThat(menu.get(i).getName()).isEqualTo(dishNames.get(i));
    }
}

 

 

3.2 스트림 평면화

 

flatMap()

flatMap 메서드는 여러개의 스트림 또는 배열을 하나의 스트림으로 만들어 반환합니다.

아래의 그림과 같이 flatMap은 각 배열을 스트림의 콘텐츠로 매핑합니다.

 

@DisplayName("flatMap 사용")
@Test
public void useFlatMap() {
    //given
    List<String> words = Arrays.asList("Modern", "Java", "In", "Action");

    //when
    List<String> result = words.stream()
            .map(word -> word.split(""))
            .flatMap(splitArr -> Arrays.stream(splitArr))
            .distinct()
            .collect(Collectors.toList());

    //then
    //맵에 character가 없으면 pass , 있으면 테스트 실패
    Map<String, String> validationMap = new HashMap<>();
    result.stream().forEach(character -> {
        assertThat(validationMap.getOrDefault(character, null)).isEqualTo(null);
        validationMap.put(character, character);
    });
    System.out.println(result);
}

 

4. 검색, 매칭

 

- Predicate가 적어도 한 요소와 일치하는 지 확인. - anyMatch()

anyMatch는 스트림에서 Predicate가 일치하는 요소를 찾고, 찾으면 쇼트서킷이 발생합니다.

불리언을 반환하므로 최종연산입니다.

 

@DisplayName("프레디케이트가 적어도 한 요소와 일치하는지 확인")
@Test
public void useAnyMatch() {

    //when
    if (menu.stream().anyMatch(dish -> dish.isVegetarian())) {
        System.out.println("이 메뉴는 채식 요리 입니다.");
    }

    //then
    assertThat(menu.stream().anyMatch(dish -> dish.isVegetarian())).isEqualTo(true);
}

 

- Predicate가 모든 요소와 일치하는지 확인. - allMatch()

allMatch()는 스트림의 모든 요소가 Predicate와 일치하는 지 확인합니다.

한 가지 요소라도 일치하지 않는다면 쇼트서킷이 발생되고, False가 리턴됩니다.

불리언을 반환하므로 최종연산입니다.

빈 스트림을 받았을 경우 true를 리턴합니다. <<<< 굉장히 중요!

 

@DisplayName("프레디케이트가 모든 요소와 일치하는지 검사")
@Test
public void useAllMatch() {

    //when
    if (menu.stream().allMatch(dish -> dish.getCalories() < 1000)) {
        System.out.println("이 메뉴는 건강식 입니다.");
    }

    //then
    assertThat(menu.stream().allMatch(dish -> dish.getCalories() < 1000)).isEqualTo(true);

}

 

- predicate가 모든 요소와 일치하지 않는지 확인 - noneMatch()

noneMatch()는 스트림의 요소가 모두 Predicate와 일치하지 않는지 확인하고, 

하나라도 일치하는 요소가 확인되면 쇼트서킷이 발생하고, False가 리턴됩니다.

불리언을 반환하므로 최종연산입니다.

 

@DisplayName("주어진 프레디케이트가 일치하는 요소가 없는지 확인.")
@Test
public void useNoneMatch() {

    //when
    if (menu.stream().noneMatch(dish -> dish.getCalories() >= 1000)) {
        System.out.println("이 메뉴는 건강식입니다.");
    }

    //then
    assertThat(menu.stream().noneMatch(dish -> dish.getCalories() >= 1000)).isEqualTo(true);
}

 

요소검색

- 현재 스트림에서 임의의 요소를 반환. - findAny

findAny()는 현재 스트림에서 임의의 요소를 반환합니다.

 

@DisplayName("요소검색")
@Test
public void findAnyElement() throws NoSuchElementException {
    //when
    Optional<Dish> dish = menu.stream().filter(d -> d.isVegetarian()).findAny();
    //then
    if (dish.isPresent()) {
        dish.ifPresent(d -> assertThat(d.isVegetarian()).isEqualTo(true));
    } else {
        throw new NoSuchElementException();
    }

    /**
     * Optional Class의  메서드 사용해보기
     */
    //isPresent() : 존재하면 true 반환 없으면 false
    boolean present = menu.stream().filter(d -> d.isVegetarian()).findAny().isPresent();

    //ifPresent(Consumer<T>) : 존재하면 주어진 블록을 실행
    menu.stream().filter(d -> d.isVegetarian()).findAny().ifPresent(d -> System.out.println(d.getName()));

    //T get() : 존재하면 반환, 없으면 NoSuchElement 반환
    Dish dish1 = menu.stream().filter(d -> d.isVegetarian()).findAny().get();

    //T orElse(T other) : 값이 존재하면 값을 반환 값이 없으면 기본값 반환.
    Dish dish2 = menu.stream().filter(d -> d.isVegetarian()).findAny().orElse(new Dish());
}

 

- 첫번째 요소 찾기 - findFirst()

findFirst()는 스트림에서 가장 첫번째 요소를 반환합니다.

 

@DisplayName("첫 번째 요소 찾기")
@Test
public void findFirstElement() {
    //given
    List<Integer> listOfNumber = Arrays.asList(1, 2, 3, 4, 5);

    //when
    Optional<Integer> firstSquareDivisibleByThree = listOfNumber
            .stream()
            .filter(x -> x / 3 == 0)
            .map(x -> x * x)
            .findFirst();

    //then
    if (firstSquareDivisibleByThree.isPresent()) {
        firstSquareDivisibleByThree.ifPresent(number -> Assertions.assertThat(Math.sqrt(number) / 3).isEqualTo(0));
    } else {
        throw new NoSuchElementException();
    }
}

 

5. 리듀싱

 스트림의 모든 요소를 처리해서 값으로 도출하는 것을 리듀싱 연산이라고 합니다. 

 

함수형 프로그래밍에서는 이 과정을 종이를 작은조각이 될 때 까지

반복해서 접는 것과 비슷하다는 의미로 폴드(fold)라고 부릅니다.

 

리듀싱을 이용하여 요소의 합, 곱 또는 요소의 최댓값, 최솟값을 구할 수 있습니다.

/**
 * 5.5.1 요소의 합
 * problem : 스트림의 각 요소의 합을 구해라
 * logic : for문 => 스트림으로 변환
 * expected result : sum of stream elements
 * validation : 외부반복을 이용한 요소의합, 요소의곱과 스트림을 이용한 요소의합, 요소의 곱을 비교.
 */
@DisplayName("요소의 합")
@Test
public void sumOfElements() {
    /*
     *  외부 반복을 통한 요소의 합
     */
    int[] numbers = new int[]{4, 5, 3, 9};
    int sum = 0;
    for (int x : numbers) {
        sum += x;
    }
    /*
     *  외부 반복을 통한 요소의 곱
     */
    int multiply = 1;
    for (int x : numbers) {
        multiply *= x;
    }


    /*
     *  스트림을 사용한 요소의 합
     */
    int sumOfStream = Arrays.stream(numbers).reduce(0, Integer::sum);

    /*
     *  스트림을 사용한 요소의 곱
     */
    int multiplyOfStream = Arrays.stream(numbers).reduce(1, (a, b) -> (a * b));

    /*
     *  초깃값이 없는 reduce의 경우 Optional 타입으로 반환된다.
     */
    OptionalInt sumOfStreamNoInit = Arrays.stream(numbers).reduce((a, b) -> a + b);




    /*
     *  검증
     */
    Assertions.assertThat(sumOfStream).isEqualTo(sum);          // 초기값이 있는 요소의 합
    Assertions.assertThat(multiplyOfStream).isEqualTo(multiply);    //초기값이 있는 요소의 곱
    Assertions.assertThat(sumOfStreamNoInit.getAsInt()).isEqualTo(sum);      //초기값이 없는 요소의 합

}

/**
 * 5.5.1 요소의 합 - 최댓값, 최솟값
 * problem : 리스트에서 최댓값과 최솟값을 구해라
 * logic : find min and max value by using stream.
 * expected result : min value and max value
 * validation : array에서 구한 max, min과 List에서 구한 max, min을 비교
 */
@DisplayName("요소의 합")
@Test
public void findMinMax() {
    //given
    int[] numbers = new int[]{4, 5, 3, 9};
    List<Integer> nList = Arrays.stream(numbers).boxed().collect(Collectors.toList());

    //when
    int maxFromArr = Arrays.stream(numbers).reduce(Math::max).orElseThrow();
    int minFromArr = Arrays.stream(numbers).reduce(Math::min).orElseThrow();

    Integer maxFromList = nList.stream().reduce(Math::max).orElseThrow();
    Integer minFromList = nList.stream().reduce(Math::min).orElseThrow();

    //then
    Assertions.assertThat(maxFromArr).isEqualTo(maxFromList);
    Assertions.assertThat(minFromArr).isEqualTo(minFromList);


}

 

6. 숫자형 스트림

 

 기본형 특화 스트림

스트림에서는 숫자를 효율적으로 처리할 수 있도록 기본형 특화 스트림(primitive stream specialization)을 제공합니다.

 

기본형 특화 스트림을 사용하게 되면 박싱비용을 피할 수 있습니다.

 

기본형 특화 스트림으로는 

 

int 요소에 특화된 IntStream

double 요소에 특화된 DoubleStream

long 요소에 특화된 LongStream

 

을 제공합니다.

 

각각의 인터페이스는 sum, max등 숫자관련 리듀싱 연산을 수행하는 메서드를 제공합니다.

 

/**
 * 5.7.1 기본형 특화 스트림 - 숫자 스트림으로 매핑
 * problem : 메뉴의 칼로리 합계 구하기
 * logic : stream - mapToInt - sum
 * expected result : integer value
 * validation : 합이 맞는지 확인
 */
@DisplayName("숫자스트림으로 매핑")
@Test
public void useNumberTypeStream() {
    //when
    int sumOfCalStream = menu.stream()
            .mapToInt(Dish::getCalories)
            .sum();

    int sumOfCal=0;
    for (Dish dish : menu) {
        sumOfCal+=dish.getCalories();
    }

    //then
    Assertions.assertThat(sumOfCalStream).isEqualTo(sumOfCal);
}

/**
 * 5.7.1 기본형 특화 스트림 - 객체 스트림으로 매핑
 * problem : boxed 이용해보기
 * logic : stream - mapToInt - boxed
 * expected result : integer value
 * validation : x
 */
@DisplayName("객체 스트림으로 매핑")
@Test
public void useBoxed(){
    //when
    Stream<Integer> boxed = menu.stream().mapToInt(Dish::getCalories).boxed();
    Integer sumOfCal = boxed.reduce(0, (d1, d2) -> d1 + d2);

    System.out.println(sumOfCal);
}

/**
 * 5.7.1 기본형 특화 스트림 - 기본값 OptionalInt
 * problem : OptionalInt 사용해보기
 * logic : stream - mapToInt - max
 * expected result : OptionalInt value
 * validation : 최댓값인지 검증
 */
@DisplayName("기본값 OptionalInt 사용법 익히기")
@Test
public void useOptionalInt(){
    //when
    OptionalInt optionalInt = menu.stream().mapToInt(Dish::getCalories).max();
    int maxVal = optionalInt.orElseThrow();

    int maxValForEach =0;
    for (Dish dish:menu){
        maxValForEach = dish.getCalories() > maxValForEach ? dish.getCalories() : maxValForEach;
    }
    //then
    Assertions.assertThat(maxVal).isEqualTo(maxValForEach);
}

숫자 범위

스트림에서는 특정 범위의 숫자를 이용할 수 있도록 range(), rangeClosed() 를 제공한다.

 

range(1, 100) : 1 이상 100 미만

rangeClosed(1,100) : 1이상 100이하

 

/**
 * 5.7.2 숫자 범위
 * problem : 1 부터 100사이의 짝수를 구해라
 * logic : IntStream - rangeClosed - filter - count
 * expected result : long
 * validation : 1-100까지의 짝수는 50개이므로 cnt가 50인지 비교
 */
@DisplayName("1부터 100까지 사이에 짝수 구하기")
@Test
public void findEvenNumberBetween1And100(){
    //when
    //이상, 미만
    long count = IntStream.range(0, 100).filter(number -> number % 2 == 0).count();
    //이상, 이하
    long cnt = IntStream.rangeClosed(2, 100).filter(number -> number % 2 == 0).count();

    //then
    System.out.println(count);
    System.out.println(cnt);
    Assertions.assertThat(count).isEqualTo(50);
    Assertions.assertThat(cnt).isEqualTo(50);
}

 

7. 스트림의 다양한 활용법 및 메서드

 

값으로 스트림 만들기

Stream.of()

/**
     * 5.8.1 값으로 스트림 만들기
     * problem : 임의의 수를 인수로 받는 정적 메서드 Stream.of를 이용하여 스트림을 만들자.
     * logic : Stream.of()
     */
    @DisplayName("값으로 스트림 만들기")
    @Test
    public void valueToStream(){
        //given
        Stream<String> stream = Stream.of("Modern", "Java", "In", "Action");

        //when
        stream.map(string -> string.toLowerCase()).forEach(s-> System.out.println(s));

        //Stream.empty()를 이용해 스트림 비우기
        Stream<String> emptyStream = Stream.empty();
//        stream.map(string -> string.toLowerCase()).forEach(s-> System.out.println(s));

    }

 

null이 될 수 있는 객체로 스트림 만들기

Stream.ofNullable()

/**
 * 5.8.2 null이 될 수 있는 객체로 스트림 만들기
 * problem : 자바 9에서 추가된 null이 될 수 있는 개체를 스트림으로 만들 수 있는 메서드를 활용해보자
 */
@DisplayName("null이 될 수 있는 객체로 스트림 만들기")
@Test
public void useStreamOfNullable(){
    //적용 전
    String home = System.getProperty("home");
    Stream<String> notEmpty = home == null ? Stream.empty() : Stream.of("notEmpty");

    //적용 후
    Stream<String> values = Stream.ofNullable(System.getProperty("home"));

    //flatMap과 함께 사용
    Stream.of("config", "home", "user")
            .flatMap(key -> Stream.ofNullable(System.getProperty(key)));

}

 

배열로 스트림 만들기

Arrays.stream(배열)

/**
 * 5.8.3 배열로 스트림 만들기
 */
@DisplayName("배열로 스트림 만들기")
@Test
public void arrayToStream(){
    //given
    int[] arr = new int[] {1,2,3,4,5,6,7,8,9,10};
    //when
    int sum = Arrays.stream(arr).sum();

    //then
    Assertions.assertThat(sum).isEqualTo(55);
}

파일로 스트림 만들기

/**
 * 5.8.4 파일로 스트림 만들기
 */
@DisplayName("파일로 스트림 만들기")
@Test
public void fileToStream(){
    //given
    long uniqueWords = 0;
    try(Stream<String> lines =
            Files.lines(Paths.get("data.txt"), Charset.defaultCharset())) {
        uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
                .distinct()
                .count();
    } catch (IOException e) {
    }
}

함수로 무한스트림 만들기

스트림 API는 함수에서 스트림을 만들수 있는 두 정적 메서드 Stream.iterate와 Stream.generate를 제공합니다.

iterate 메서드

iterate는 요청할 때 마다 값을 생산할 수 있으며 끝이 없으므로 무한(infinite stream)스트림을 만듭니다.

또한 이러한 스트림을 언바운드(unbounded stream) 스트림이라고 합니다.

 

/**
 * 5.8.5 무한 스트림 만들기
 */
@DisplayName("무한 스트림 만들기")
@Test
public void useUnlimitedStream(){
    Stream.iterate(0, n-> n+2)
            .limit(20)
            .forEach(n -> System.out.println(n));
}

/**
 * 퀴즈 5.4 피보나치수열 집합을 만들자. / iterate - 값을 받아 새로운 값을 생성. 연속적으로 계산.
 * problem : 피보나치 수열의 집합 (0,1), (1,1), (1,2) ... 을 만들어라
 * logic : Stream - iterate - limit - foreach
 */
@DisplayName("피보나치수열 집합을 만들자.")
@Test
public void makeFiboSet(){
    //use iterate
    Stream.iterate(new int[] {0,1} ,arr -> 4000> arr[0], arr -> new int[] {arr[1], arr[0]+arr[1]})
            .limit(20)
            .forEach(arr -> System.out.println("( " + arr[0] + ", "+ arr[1] + " )"));

}

/**
 * 5.8.5 iterate 메서드 - 값을 받아 새로운 값을 생성. 연속적으로 계산.
 * problem : iterate 메서드를 사용해보자
 * logic : IntStream - iterate - takeWhile - foreach
 */
@DisplayName("generate 메서드")
@Test
public void useIterate(){
    //given
    IntStream.iterate(0, n->n+4)
            .takeWhile(n -> n<100)
            .forEach(System.out::println);

    IntStream.iterate(0, n-> n+3)
            .filter(n -> n<10)
            .limit(3)
            .forEach(System.out::println);
}

 

generate 메서드

iterate와 비슷하게 generate도 요구할 때 값을 계산하는 무한 스트림을 만듭니다.

그러나 iterate와 달리 generate는 생산된 값을 연속적으로 계산하지 않습니다.

오직 supplier를 받아 새로운 값을 실행합니다.

 

supplier를 이용해 과거의 값을 저장하면서 프로그래밍 할 수도 있지만,

이렇게 되면 병렬코드에서는 문제가 생기게 됩니다.

/**
 * 5.8.5 generate 메서드 - iterate와 마찬가지로 값을 받아 새로운 값을 생성 but, 연속적으로 계산하지 않음.
 * problem : generate 메서드를 사용해보자
 * logic :  Stream - generate - limit - for
 */
@DisplayName("generate 메서드")
@Test
public void useGenerate(){
    //given
    Stream.generate(() -> Math.random())
            .limit(10)
            .forEach(System.out::println);

}

/**
 * 5.8.5 generate 메서드 - iterate와 마찬가지로 값을 받아 새로운 값을 생성 but, 연속적으로 계산하지 않음.
 * problem : generate 메서드를 사용하여 피보나치 수열을 만들어보자.
 *          -> 이 문제에서는 상태를 가진 Supplier를 사용하지만 병렬코드에서는 supplier에 상태가 있으면 안전하지 않다.
 *           그러므로 실제로는 사용하면 안되는 코드이다.
 * logic :  IntSupplier를 가변성있게 선언한 후 IntStream - generate
 */
@DisplayName("gernerate 메서드를 이용한 피보나치 수열")
@Test
public void gernerateFibo(){
    //given
    IntSupplier fibo = new IntSupplier(){
        private int firstOne = 0;
        private int nextOne = 1;

        @Override
        public int getAsInt() {
            int oldOne = this.firstOne;
            this.firstOne = this.nextOne;
            this.nextOne = this.firstOne + oldOne;
            return oldOne;
        }
    };

    //when
    IntStream.generate(fibo).limit(10).forEach(s -> System.out.println(s));
}

 

최종 정리

스트림을 이용하면 복잡한 데이터 연산을 질의를 통해 좀 더 가독성있게 표현할 수 있습니다.

 

filter(Predicate), distinct(), takeWhile(Predicate), dropWhile(Predicate), skip(n), limit(n) 을 통해 스트림을 필터링 하거나 슬라이싱 할 수 있습니다. 

 

정렬된 스트림일 경우에 takeWhile(Predicate)과 dropWhile(Predicate)을 효과적으로 사용 할 수 있다.

 

 map(function), flatMap(Array) 메서드로 스트림의 요소를 변환, 매핑 할 수 있다.

 

findFirst(), findAny() 로 스트림의 특정 요소를 찾을 수 있다.

 

allMatch(Predicate), anyMatch(Predicate), noneMatch(Predicate) 를 통해 주어진 프레디케이트와 일치하는 요소들로 이루어진 스트림인지 확인 할 수 있다.

 

reduce(초기값, function) 를 이용하여 리듀싱 연산을 수행 할 수 있다.

 

filter(Predicate), map(function)stateless operation입니다.

reduce(초기값, function)  연산은 값을 계산하는데 필요한 상태를 저장합니다.

sorted(), distinct() 등의 메서드는 새로운 스트림을 반환하기 전 스트림의 모든 요소를 버퍼에 저장합니다.

이와 같이 상태를 저장하는 연산을 stateful operation이라고 합니다.

 

IntStream, DoubleStream, LongStream은 기본형 특화 스트림 입니다.

이들 연산은 기본형의 박싱비용을 줄여줍니다.

 

컬렉션 뿐만아니라 값(String, int 등등), 배열, 파일, iterate(초기값, 조건, function), generate(supplier) 같은 메서드를 이용해서도 스트림 생성이 가능합니다.

 

값의 끝이 없는 스트림을 무한 스트림(infinite stream)이라 하고

언바운드 스트림(unbounded stream)이라고도 합니다.

 

참고 

  • 모던 자바 인 액션