인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

따뜻한 당나귀님의 프로필 이미지

작성한 질문수

김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍

스트림 API란?

Stream.of(names).forEach(System.out::println) 과 names.stream.forEach(System.out::println) 작동방식의 차이

해결된 질문

작성

·

181

·

수정됨

1

 학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문 전에 다음을 꼭 확인해주세요.


1. 강의 내용과 관련된 질문을 남겨주세요.
2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.
(자주 하는 질문 링크: https://bit.ly/3fX6ygx)
3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.
(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)

질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.
=========================================
[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (예/아니오)
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)
3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)

[질문 내용]

저는 List<String> names = List.of("Kim", "Lee", "Park", "Kang");
이라는 리스트를 만들고 Stream 을 통해 forEach를 돌리려고 했습니다.

두 가지 경우로 만들었는데.

  1. names.stream.forEach(System.out::println)

  2. Stream.of(names).forEach(System.out::println);

여기서 첫번째방법의 결과값은 한줄마다 값들이 출력되어 나왔습니다.

Kim

Lee

Park

Kang

하지만 두번째방법의 결과값은 첫번째 방법과 똑같다고 예상했던 것과 달리 리스트형식의 toString 으로 출력되었습니다.

[Kim, Lee, Park, Kang]

이때, Stream.of(List).foreach() 와 (List).stream.foreach() 의 작동방식의 차이를 알고 싶습니다.

 

답변 2

4

dev.rudevico님의 프로필 이미지

재현님 답변에 자세한 설명을 더해서 하겠습니다.


1. 리턴 타입 확인

일단 리턴 타입을 확인하기 위해 names.stream(), Stream.of(names) 코드만 작성하여 Stream<String> 변수에 담아보도록 하겠습니다.

import java.util.*;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        List<String> names = List.of("Kim", "Lee", "Park", "Kang");
        // return: Stream<String>
        Stream<String> stream1 = names.stream();
        // return: Stream<List<String>>
        Stream<List<String>> stream2 = Stream.of(names);
//        Stream<String> stream2 = Stream.of(names); // type error
    }
}

전자의 경우: 질문자님이 예상한 것과 같이 Stream<String> 타입의 변수에 담기지만,

후자의 경우: 타입이 맞지 않아 Incompatible types 컴파일 에러가 발생하고, 그 메시지는 다음과 같습니다.

  • 메시지: Incompatible types.

    • Found: 'java.util.stream.Stream<java.util.List<java.lang.String>>',

    • required: 'java.util.stream.Stream<java.lang.String>'

즉 후자의 경우에는 예상과는 다르게 Stream<List<String>> 타입이 리턴된다는 것인데요. 이와 관련해서는 names.stream()Stream.of(names) 각 메서드 구현에서 설명이 되어있습니다.


2. 각 메서드의 구현 확인

(설명에 필요한 부분만 일부 잘라서 가져왔습니다.)

 

Collection.stream() 메서드 - 컬렉션 프레임워크 구현체들이 사용할 수 있는 인스턴스 메서드(Collection.java에 위치)

/**
  * Returns a sequential {@code Stream} with this collection as its source.
  *
  * @return a sequential {@code Stream} over the elements in this collection
  * @since 1.8
  */
default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}
  • 해당 메서드는 Stream<E>를 리턴합니다.

  • 주석을 보면 "a sequential {@code Stream} over the elements in this collection" 라는 부분이 있습니다.

    • 컬렉션구현체.stream()으로 호출을 하면 해당 컬렉션구현체의 elements들에 대한 Stream을 리턴한다고 합니다.

    • 조금 비약이 있지만, 컬렉션구현체 껍데기는 무시하고, 그 내부의 요소들만 빼내서 Stream으로 만든다는 것입니다. 이 Stream<E>의 타입은 당연히 컬렉션구현체의 타입과 동일하겠죠.

    • List<String> names = List.of("Kim", "Lee", "Park", "Kang");에 대해서


      names.stream()을 호출하면, List 구현체인 names의 안에 들은 String 타입의 요소들 "Kim", "Lee", "Park", "Kang"만 빼와서 Stream<String>을 생성합니다.

  • 따라서 forEach(System.out::println);로 내부 반복을 돌리면 Stream의 각 요소인 String 타입들에 대해서 수행합니다.

 

Stream.of() 메서드 - Stream 클래스의 정적 팩토리 메서드 (Stream.java에 위치)

/**
  * Returns a sequential ordered stream whose elements are the specified values.
  *
  * @param <T> the type of stream elements
  * @param values the elements of the new stream
  * @return the new stream
  */
public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}
  • 해당 메서드는 T 타입의 values를 인자로 받아서 Stream<T>를 리턴합니다.

  • 주석을 보면 다음 문장들이 있습니다.

    • Returns a sequential ordered stream whose elements are the specified values.
      -> values를 요소로 가지는 Stream을 리턴한다.

    • @param values the elements of the new stream
      -> 새로운(즉 리턴할) Stream의 요소들인 values를 파라미터로 가진다.

  • 이는 Stream.of(values...)과 같이 values를 받아서 values 자체를 Stream의 요소로 사용한다는 것입니다.

    • 이는 위의 Collection.stream()에서 [ 컬렉션구현체 껍데기는 무시하고, 그 내부의 요소들만 빼내서 ]라고 언급했던 것과는 정반대입니다.
      인자로 List 등의 컬렉션구현체가 오거나, 1, 2 등의 숫자값이 오거나, "A", "B" 등의 문자열이 오거나 뭐가 오는지에 관계없이 [ 인자로 온 것 그 자체Stream의 요소로 사용하겠다 ]는 것입니다.

    List<String> names = List.of("Kim", "Lee", "Park", "Kang");
    // 따라서 이 경우에는 인자로 온 List<String> 그 자체를 Stream의 요소로 사용합니다.
    // 따라서 Stream<List<String>>이 리턴됩니다.
    // <>가 중첩되어서 헷갈린다면 그냥 Stream<리스트>라고 생각하셔도 되겠습니다.
    Stream<List<String>> stream2 = Stream.of(names);
  • 따라서 Stream.of(names).forEach(System.out::println); 코드는 Stream<리스트>의 각 요소 (이 경우에는 List<String> names 하나)에 대해서 콘솔출력을 수행합니다.

    • 이는 System.out.println(names);와 동일한 동작을 수행합니다. 이때 모든 컬렉션 프레임워크 구현체들은 toString()이 구현되어 있으므로(아래 참고) 이를 호출합니다.

    • 따라서 출력: [Kim, Lee, Park, Kang]


모든 컬렉션 프레임워크 구현체가 가지는 toString() - 위치: AbstractCollection.java

public String toString() {
    Iterator<E> it = iterator();
    if (! it.hasNext())
        return "[]";

    StringBuilder sb = new StringBuilder();
    sb.append('[');
    for (;;) {
        E e = it.next();
        sb.append(e == this ? "(this Collection)" : e);
        if (! it.hasNext())
            return sb.append(']').toString();
        sb.append(',').append(' ');
    }
}

 

 

dev.rudevico님의 프로필 이미지

결론

  • 컬렉션구현체.stream(): 컬렉션 내부의 요소들로 스트림을 생성하여 리턴

  • Stream.of(컬렉션구현체): 컬렉션구현체 그 자체로 스트림을 생성하여 리턴

따뜻한 당나귀님의 프로필 이미지

감사합니다 큰 도움이 되었습니다.

0

재현님의 프로필 이미지

1. names.stream() -> List의 메서드

2. Stream.of() -> Stream의 정적 메서드

1번은 리스트 내의 값을 개별적으로 처리가능한 Stream<String>타입으로 반환

2번은 매개변수로 전달된 변수의 타입으로 스트림 생성

-> names는 List<String>타입이므로 Stream<List<String>>타입으로 반환

그래서 1번은 4번 반복되고 2번은 1번만 반복되는 것 같습니다