해결된 질문
작성
·
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를 돌리려고 했습니다.
두 가지 경우로 만들었는데.
names.stream.forEach(System.out::println)
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
재현님 답변에 자세한 설명을 더해서 하겠습니다.
일단 리턴 타입을 확인하기 위해 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)
각 메서드 구현에서 설명이 되어있습니다.
(설명에 필요한 부분만 일부 잘라서 가져왔습니다.)
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(' ');
}
}
0
1. names.stream() -> List의 메서드
2. Stream.of() -> Stream의 정적 메서드
1번은 리스트 내의 값을 개별적으로 처리가능한 Stream<String>타입으로 반환
2번은 매개변수로 전달된 변수의 타입으로 스트림 생성
-> names는 List<String>타입이므로 Stream<List<String>>타입으로 반환
그래서 1번은 4번 반복되고 2번은 1번만 반복되는 것 같습니다
결론
컬렉션구현체.stream()
: 컬렉션 내부의 요소들로 스트림을 생성하여 리턴Stream.of(컬렉션구현체)
: 컬렉션구현체 그 자체로 스트림을 생성하여 리턴