inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

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

스트림 API란?

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

267

따뜻한 당나귀

작성한 질문수 3

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() 의 작동방식의 차이를 알고 싶습니다.

 

java 함수형-프로그래밍 lambda optional

답변 2

5

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(' ');
    }
}

 

 

2

dev.rudevico

결론

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

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

2

따뜻한 당나귀

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

2

ssshhh

상세한 답변에 저도 많이 도움이 되었습니다

  • 이 부분은 names.stream()과 Stream.of(names)이 같게 동작한다고 영한님이 잠시 착각하고 강의 하신 것 같아요

  • 52. 파이프라인 구성 5:23 부분에서 비슷한 실수를 하시는 것 같아요. 참고 하시면 강의 듣는데 조금 도움이 되실 수 있을 것 같습니다

1

재현

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

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

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

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

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

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

db sql vs java stream 비교 궁금증 알려주세요!

1

70

1

파라미터 구분이 안되는지..?

0

68

1

자바 21 버전을 꼭 써야하나요??

0

143

2

9. 스트림 API3-컬렉터 오타?

0

78

2

(추석 챌린지) 복습 정리내용

0

94

1

람다 궁금한 점

0

73

2

스트림과 for문 질문

0

41

1

디폴트 메서드를 사용한 리펙토링

0

51

1

실무에서의 커스텀 Collector 사례 여부

0

63

1

IntelliJ 한글 깨지는 현상 문의

0

198

1

함수형 프로그래밍 적용 범위/원칙

0

59

1

스트림에 대해서 질문(float)은 사용하는지

0

55

1

[자료 패키지 누락] 2.람다 자료에서 문제 패키지명 생략 (25page)

0

60

2

지연 평가

0

68

1

메서드 시그니처

0

100

3

고급편 우선순위

0

129

3

스프링 데이터 JPA 인터페이스에서의 default 메서드 사용

0

112

2

독립적인 스레드 할당 시 스레드 개수는 어떻게 할당해야 할까요?

0

85

1

병렬 프레임 병목 현상 해결책에 대해 궁금한 점

0

102

1

스트림 중복 에러 관련 질문

0

69

1

자바 버전이 31 까지 업데이트 된다면 새로운 자바 강의를 추가 하실 건가요?

0

132

1

임계값과 스레드수를 줄일 경우 교착상태가 발생할수 있는건가요?

0

100

2

변수 합치기에 대해서 질문드립니다!

0

80

2

flatMap 응용 방법에 대해서, 이렇게 하는게 맞는지?

0

122

1