빌드노트
int 배열을 Stream.of에 넣었는데 count()가 1이다
Stream.of(int[])는 배열 한 개를 담는 Stream<int[]>를 만든다. 같은 세 값으로 원소 수·요소 타입·합계와 Integer[]의 차이를 직접 비교하고, 숫자별 연산에는 Arrays.stream을, Integer 스트림이 필요할 때는 boxed()를 선택한다.
int[] numbers = {10, 20, 30}을 Stream.of(numbers)에 넣었는데 count()가 1이라면, 이 스트림의 원소는 숫자 세 개가 아니라 배열 한 개다. 타입은 Stream<int[]>이고 map()의 람다에도 int[]가 전달된다. 각 숫자를 처리하려면 생성 지점을 Arrays.stream(numbers)로 바꾼다. 이 호출은 int 값을 내보내는 IntStream을 만든다. Arrays.stream(int[])의 반환 타입 (새 탭에서 열림)
배열 길이와 스트림의 원소 수를 나란히 확인해 보자. JDK 17 이상이 설치된 터미널에서 java -version으로 버전을 확인하고 jshell --feedback silent를 실행한 뒤 다음을 입력한다. 외부 라이브러리는 필요 없다. 뒤의 예제도 같은 JShell 세션에서 이어서 실행한다.
import java.util.Arrays;
import java.util.stream.Stream;
int[] numbers = {10, 20, 30};
System.out.println(Stream.of(numbers).count());
System.out.println(Stream.of(numbers).findFirst().orElseThrow().getClass().getTypeName());
System.out.println(Stream.of(numbers).map(n -> n.length).toList());
Windows의 OpenJDK 17.0.2에서 직접 실행한 출력이다.
1
int[]
[3]
첫 줄은 스트림의 원소 수, 둘째 줄은 첫 원소의 실제 타입이다. 마지막 줄의 [3]은 배열의 길이를 담은 결과 목록이다. map()이 받은 n은 배열이므로 n.length가 된다. 같은 입력으로 map(n -> n + 1)을 별도 컴파일했을 때는 +의 두 피연산자가 int[]와 int라는 오류가 났다. 숫자를 다루는 람다로 고치기 전에 스트림을 만드는 호출부터 확인할 상황이다.
각 종료 연산 앞에서 Stream.of(numbers)를 다시 호출했다. 이미 소비한 스트림을 재사용한 결과가 아니다. 새로 만들어도 처음부터 배열 한 개를 담으므로 원소 수는 1이다.
숫자별 연산은 Arrays.stream에서 시작한다
같은 numbers를 유지하고 생성 호출만 바꿔, 세 정수의 개수·합계와 각 값을 두 배로 만든 결과를 확인한다.
System.out.println(Arrays.stream(numbers).count());
System.out.println(Arrays.stream(numbers).sum());
System.out.println(Arrays.toString(Arrays.stream(numbers).map(n -> n * 2).toArray()));
출력은 차례로 3, 60, [20, 40, 60]이었다. 이번 map()의 n은 개별 int이므로 곱셈이 가능하다. 합계만 필요하면 두 번째 줄처럼 Arrays.stream(numbers).sum()을 사용하면 된다. count()와 sum()을 같은 스트림 변수에 연달아 호출하지 않고 각각 새 스트림에서 시작한다.
자기 코드에서 여전히 count()가 1이면 호출이 Arrays.stream(numbers)로 바뀌었는지와 numbers의 선언 타입이 int[]인지 먼저 확인한다. 이미 필터가 붙어 있다면 이 예제처럼 필터 이전의 새 스트림으로 개수를 비교해야 입력 문제와 조건에 따른 제외를 구별할 수 있다.
Integer[]에서는 왜 세 개가 나올까
값은 그대로 두고 배열 타입만 Integer[]로 바꾸면 Stream.of()의 결과가 달라진다.
Integer[] boxedNumbers = {10, 20, 30};
System.out.println(Stream.of(boxedNumbers).count());
System.out.println(Stream.of(boxedNumbers).toList());
실제 출력은 3과 [10, 20, 30]이었다. 이 호출의 결과는 Stream<Integer>다. Stream.of 문서 (새 탭에서 열림)에는 원소 하나를 받는 of(T)와 가변 인자를 받는 of(T...)가 있다. Integer[]는 T가 Integer인 T[] 자리에 전달될 수 있어 배열 안의 세 참조가 스트림 원소가 된다.
반면 int[]를 같은 방식으로 해석하려면 T가 int여야 하는데, 제네릭의 타입 인자로 원시 타입을 쓸 수는 없다. int[] 자체는 참조 타입이므로 이 호출에서는 T가 int[]인 단일 원소 오버로드가 선택된다. JLS의 타입 인자 규칙 (새 탭에서 열림)과 배열도 객체라는 정의 (새 탭에서 열림)를 함께 보면 두 결과를 구별할 수 있다.
int 하나가 Integer로 자동 박싱되는 것과 int[] 전체가 Integer[]로 바뀌는 것은 다르다. JLS의 박싱 변환 목록 (새 탭에서 열림)에는 배열 전체를 그렇게 바꾸는 변환이 없다. 따라서 Integer[]에서 성공한 코드를 int[]에도 그대로 적용할 수 있다고 판단하면 안 된다.
Stream<Integer>가 필요할 때만 boxed를 붙인다
후속 코드가 Stream<Integer>를 요구한다면, 각 정수를 내보내는 스트림을 만든 다음 boxed()로 변환한다.
Stream<Integer> boxed = Arrays.stream(numbers).boxed();
System.out.println(boxed.toList());
System.out.println(Arrays.stream(numbers).boxed().findFirst().orElseThrow().getClass().getTypeName());
출력은 [10, 20, 30]과 java.lang.Integer였다. IntStream.boxed() 문서 (새 탭에서 열림)가 설명하는 변환도 각 int를 Integer로 바꾸는 것이다. 숫자의 합계나 숫자별 계산이 목적이면 앞의 IntStream에서 처리할 수 있다. 배열 자체를 원소 하나로 전달하려는 코드라면 Stream.of(numbers)의 결과가 그 의도에 맞는다.
직접 실행한 범위는 OpenJDK 17.0.2에서 세 값을 담은 int[]와 Integer[]의 순차 처리다. Java 21의 Stream.of (새 탭에서 열림), Arrays.stream (새 탭에서 열림), IntStream.boxed (새 탭에서 열림)에서도 같은 계약을 확인했지만 Java 21 실행과 병렬 처리·성능 비교는 하지 않았다. JShell 확인을 마치면 /exit로 종료한다.