기본 콘텐츠로 건너뛰기

방송대 Java프로그래밍 12강: 스트림 파이프라인과 중간·종료 연산

Java프로그래밍 12강 - 컬렉션과 스트림

방송대 Java프로그래밍 12강: 스트림 파이프라인과 중간·종료 연산

스트림 코드는 한 줄로 이어져 있어도 모든 메서드가 같은 일을 하지는 않는다. 소스는 원소의 흐름을 열고, 중간 연산은 무엇을 거르고 바꿀지 연결하며, 종료 연산이 실제 처리를 시작해 결과를 만든다. 이 관계를 놓치면 “코드를 썼는데 왜 실행되지 않는가”, “왜 일부 원소만 출력되는가”, “왜 같은 스트림을 다시 쓸 수 없는가”를 설명하기 어렵다. 여기서는 컬렉션·배열·파일에서 출발한 데이터가 필터링·매핑·정렬을 거쳐 집계·매칭·수집으로 끝나는 전체 구조를 실행 순서까지 추적한다.

스트림은 세 구역을 잇는 데이터 처리 관계망이다

스트림은 컬렉션이나 배열 같은 데이터 소스에서 만들어진 원소의 시퀀스를 표현하고, 원소를 간결하게 처리하도록 돕는 인터페이스다. 원본 데이터를 직접 바꾸는 저장소가 아니라 원본에서 원소를 받아 처리 단계로 흘려보내는 통로에 가깝다. 관련 클래스와 인터페이스는 java.util.stream 패키지에 있다.

구역하는 일대표 API다음 단계로 넘기는 것
소스와 스트림 생성처리할 원소의 흐름을 연다stream(), Arrays.stream(), Files.lines()원본 스트림
중간 연산선택·변환·정렬 규칙을 연결한다filter(), map(), distinct(), sorted(), peek()새로운 스트림
종료 연산파이프라인을 실행해 결과나 동작을 만든다count(), forEach(), anyMatch(), collect()값·컬렉션·출력 등의 최종 결과

중간 연산은 여러 개 이어질 수 있지만 종료 연산은 파이프라인의 마지막에 한 번 놓인다. 이 구조는 메서드 체이닝으로 한 표현식에 연결할 수 있다. 스트림은 내부 반복과 함수형 프로그래밍 방식을 지원하고, 멀티코어 CPU를 활용할 수 있는 병렬 처리 방식도 제공한다.

관계의 핵심: 소스는 “무엇을 처리할지”, 중간 연산은 “어떻게 처리할지”, 종료 연산은 “어떤 결과로 끝낼지”를 정한다. 메서드 이름을 외우기 전에 어느 구역에 속하는지 먼저 판정한다.

외부 반복은 순회를 지휘하고 내부 반복은 동작을 맡긴다

외부 반복에서는 프로그램 코드가 다음 원소를 언제 가져올지 직접 제어한다. 향상된 for문이나 Iterator를 사용하면 반복 변수와 반복 조건이 코드 바깥에 드러난다. 내부 반복에서는 컬렉션이나 스트림이 원소 순회를 맡고, 프로그램은 각 원소에 수행할 동작만 람다식으로 전달한다.

List<String> names = Arrays.asList("Kim", "Lee", "Park");

// 외부 반복: 프로그램이 순회 구조를 명시한다.
for (String name : names) {
    System.out.println(name);
}

// 내부 반복: 처리할 동작을 Consumer 형태로 전달한다.
names.forEach(name -> System.out.println(name));
비교 기준외부 반복내부 반복
순회 제어호출 코드가 직접 담당데이터 구조나 스트림이 담당
프로그램이 적는 것반복 구조와 처리 코드원소별 처리 동작
대표 형태for, while, IteratorforEach(Consumer)
병렬화 관점반복 제어와 작업 분할을 직접 고려스트림 API가 처리 구조를 위임받음

컬렉션의 forEach()도 내부 반복이지만, 스트림 파이프라인의 forEach()는 종료 연산이라는 점을 구분해야 한다. 이름은 같아도 전자는 컬렉션에서 곧바로 각 원소를 처리하고, 후자는 앞에 연결된 중간 연산까지 실행시키며 파이프라인을 끝낸다.

소스에 따라 스트림을 여는 입구가 달라진다

스트림의 원소는 컬렉션, 배열, 숫자 범위, 텍스트 파일 등에서 올 수 있다. 참조형 원소에는 주로 Stream<T>를 사용하고, 기본형 숫자를 직접 다루는 특화 인터페이스로 IntStream, LongStream, DoubleStream이 있다. 이들은 모두 BaseStream 계열에 속한다.

데이터 소스생성 방법대표 결과형주의점
Collectioncollection.stream()Stream<T>일반 스트림
Collectioncollection.parallelStream()Stream<T>병렬 처리가 가능한 스트림
객체 배열Arrays.stream(array)Stream<T>배열 원소가 객체
int[]Arrays.stream(array)IntStream기본형 숫자 전용 연산 사용
정수 범위IntStream.range(), rangeClosed()IntStream끝값 포함 여부를 구분
텍스트 파일Files.lines(path)Stream<String>행 단위 스트림을 사용한 뒤 닫음
int total = IntStream.rangeClosed(1, 100).sum();
double average = IntStream.rangeClosed(1, 100)
        .average()
        .getAsDouble();

System.out.println(total);   // 5050
System.out.println(average); // 50.5

HashSet, ArrayList, LinkedList 같은 컬렉션은 stream()parallelStream()을 바로 사용할 수 있다. HashMap은 먼저 entrySet()으로 엔트리의 Set을 얻은 뒤 그 컬렉션에서 스트림을 만든다. 파일 행 스트림은 파일 자원과 연결되므로 사용 범위를 명확히 하고 닫는다.

Path path = Paths.get("data", "scores.txt");

try (Stream<String> lines = Files.lines(path)) {
    lines.forEach(line -> System.out.println(line));
}

일반 스트림과 병렬 스트림은 같은 결과 조건부터 확인한다

stream()은 일반 스트림을, parallelStream()은 병렬 처리가 가능한 스트림을 반환한다. 강의의 HashSetArrayList 예에서는 병렬 스트림의 원소가 main과 여러 작업 스레드에서 처리되는 모습을 스레드 이름으로 확인한다. 내부 반복은 처리 동작을 API에 맡기므로 작업을 나누는 구조와 결합하기 쉽다.

Set<Integer> numbers = new HashSet<>();
for (int i = 0; i < 10; i++) {
    numbers.add(i);
}

numbers.parallelStream().forEach(number ->
        System.out.println(number + " : "
                + Thread.currentThread().getName()));

병렬 실행에서는 여러 작업자가 원소를 나누어 처리하므로 forEach()의 출력 순서를 일반 스트림과 같다고 가정하지 않는다. 또한 병렬 스트림을 선택했다는 사실만으로 모든 작업이 더 빠르다고 결론 내리는 것도 적절하지 않다. 시험이나 코드 판단에서는 우선 처리 결과가 순서에 의존하는지, 각 원소의 동작이 서로 영향을 주는지부터 살핀다.

오개념 교정: 병렬 스트림의 핵심은 “람다식을 쓰면 자동으로 순서까지 보존하며 빨라진다”가 아니다. 병렬 처리가 가능한 실행 구조를 얻는 것이며, 강의 예처럼 실제 처리 순서와 담당 스레드는 달라질 수 있다.

중간 연산은 즉시 결과를 만들지 않고 처리 규칙을 연결한다

중간 연산은 원본 스트림의 원소를 필터링하거나 변환하여 새로운 스트림을 반환한다. 따라서 다음 중간 연산이나 종료 연산을 계속 연결할 수 있다. 메서드 체이닝은 “원본 스트림 → 중간 스트림 1 → 중간 스트림 2 → 최종 결과”를 한 표현식으로 나타내는 방식이다.

List<String> words =
        Arrays.asList("apple", "banana", "cherry", "Avocado");

long count = words.stream()
        .map(String::toUpperCase)
        .filter(word -> word.startsWith("A"))
        .count();

System.out.println(count); // 2

학습을 위해 실행 논리를 풀면 먼저 네 단어가 소스가 된다. map()은 각각을 APPLE, BANANA, CHERRY, AVOCADO로 바꾸고, filter()는 A로 시작하는 두 단어만 통과시킨다. 마지막 count()가 남은 원소 수 2를 만든다.

다만 종료 연산 전까지는 파이프라인이 실제로 실행되지 않는 지연 평가가 적용된다. 또한 종료 연산을 수행한 스트림은 다시 사용할 수 없다. 같은 원본으로 다른 결과가 필요하면 원본 컬렉션이나 배열에서 새로운 스트림을 만들어야 한다.

Stream<String> stream = words.stream()
        .filter(word -> word.length() > 5);

// 아직 종료 연산이 없으므로 최종 결과를 만들지 않았다.
long longWordCount = stream.count();

// 다른 처리가 필요하면 원본에서 새 스트림을 만든다.
words.stream()
        .map(String::toUpperCase)
        .forEach(word -> System.out.println(word));

필터링은 남길 원소의 조건과 중복 기준을 분리한다

filter()는 매개변수로 받은 조건식이 true인 원소만 새 스트림으로 보낸다. distinct()는 중복을 제거한다. 둘 다 원소 수를 줄일 수 있지만, 하나는 조건 만족 여부를 보고 다른 하나는 서로 같은 원소인지 판단한다.

List<String> codes =
        Arrays.asList("ABC", "BCD", "AFE", "CDE", "ABZ", "ACCZ");

codes.stream()
        .filter(code -> code.startsWith("A"))
        .filter(code -> code.endsWith("Z"))
        .filter(code -> code.length() > 3)
        .forEach(code -> System.out.println(code)); // ACCZ

첫 필터 뒤에는 ABC, AFE, ABZ, ACCZ가 남는다. 두 번째 필터는 ABZACCZ를 남기고, 길이가 3보다 큰지 검사하는 마지막 필터에서 ACCZ만 통과한다. 여러 필터를 분리하면 어느 조건에서 원소가 탈락했는지 추적하기 쉽다.

연산판단 대상람다식 필요 여부객체 원소에서의 기준
filter(predicate)각 원소가 조건을 만족하는가참·거짓을 돌려주는 조건 필요람다식이 지정한 속성·조건
distinct()앞 원소와 중복되는가별도 인수 없음hashCode()equals()에 따른 동등성

오개념 교정: 제목·저자·가격이 눈으로 같아 보이는 두 객체가 있다고 해서 distinct()가 자동으로 같은 책으로 판단하는 것은 아니다. 객체의 중복 판단이 의도대로 되려면 그 객체의 동등성 기준이 내용에 맞게 정의되어 있어야 한다.

매핑은 하나를 바꾸고 flatMap은 여러 흐름을 하나로 편다

map()은 각 원소에 함수를 적용해 다른 원소로 바꾼다. 객체 스트림을 기본형 숫자 스트림으로 바꿀 때에는 mapToInt() 등을, 기본형 숫자 스트림을 객체 스트림으로 바꿀 때에는 mapToObj()를 사용할 수 있다. IntStreamasLongStream(), asDoubleStream()은 다른 기본형 숫자 스트림으로 변환하고, boxed()int 값을 Integer 객체로 포장한 Stream<Integer>를 만든다.

String[] textNumbers = {"10", "20", "30"};

int sum = Arrays.stream(textNumbers)
        .mapToInt(text -> Integer.parseInt(text))
        .sum();

System.out.println(sum); // 60

flatMap()은 각 원소를 하나의 값이 아니라 스트림으로 바꾼 뒤, 그렇게 생긴 여러 스트림을 하나의 평평한 스트림으로 합친다. 문장 두 개를 단어 스트림 하나로 만드는 강의 예가 대표적이다.

Arrays.asList(
        "Hello world, this is java",
        "Welcome to java world")
        .stream()
        .flatMap(sentence ->
                Arrays.stream(sentence.split(" ")))
        .forEach(word -> System.out.println(word));

선택 기준: 원소 하나가 결과 하나로 바뀌면 map(), 원소 하나가 여러 원소의 흐름으로 바뀌고 그 흐름들을 하나로 합쳐야 하면 flatMap()을 선택한다.

정렬은 비교 기준을, 루핑은 파이프라인의 실행 범위를 드러낸다

sorted()는 원소를 오름차순 또는 지정한 비교 순서로 정렬한 새 스트림을 만든다. 객체의 자연 정렬을 사용하려면 원소가 Comparable을 구현하고 compareTo()로 기준을 제공해야 한다. 비교자를 인수로 전달하면 그 기준에 따라 정렬할 수 있다.

class Book implements Comparable<Book> {
    String title;
    int price;

    Book(String title, int price) {
        this.title = title;
        this.price = price;
    }

    @Override
    public int compareTo(Book other) {
        return Integer.compare(this.price, other.price);
    }
}

books.stream()
        .sorted()
        .forEach(book -> System.out.println(book.title));

peek()는 각 원소가 지나갈 때 동작을 수행하면서 새 스트림을 반환하는 중간 연산이라 디버깅이나 로깅에 활용한다. forEach()도 원소별 동작을 수행하지만 파이프라인을 끝내는 종료 연산이다. 이 차이는 뒤에 다른 연산을 더 연결할 수 있는지에서 드러난다.

강의의 가격 순서가 10,000원, 20,000원, 14,000원, 21,000원, 12,000원이라고 가정한 다음 흐름을 추적해 보자.

boolean found = books.stream()
        .peek(book -> System.out.println(book.price))
        .anyMatch(book -> book.price > 20000);
  1. 10,000원을 출력하고 조건을 검사한다. 거짓이므로 다음 원소로 간다.
  2. 20,000원과 14,000원도 출력하지만 price > 20000은 거짓이다.
  3. 21,000원을 출력한 뒤 조건이 처음 참이 된다.
  4. anyMatch()는 필요한 답을 얻었으므로 뒤의 12,000원까지 가지 않고 종료한다.

따라서 peek()가 모든 원소를 무조건 방문한다고 생각하면 안 된다. 실제 처리 범위는 뒤에 연결된 종료 연산의 요구와 단축 평가에 따라 달라진다.

종료 연산은 파이프라인을 실행하고 스트림의 사용을 끝낸다

종료 연산은 중간 처리 과정을 거친 스트림에서 집계값이나 컬렉션 같은 결과를 만들거나, 출력처럼 최종 동작을 수행한다. 종료 연산이 호출되어야 지연되어 있던 파이프라인이 실행되며, 실행을 마친 스트림은 다시 사용할 수 없다.

목적대표 종료 연산결과를 읽는 질문
개수·합계·평균·최솟값·최댓값count(), sum(), average(), min(), max()원소 전체를 하나의 집계값으로 줄이는가?
조건 일치anyMatch(), allMatch(), noneMatch()하나·모두·어느 것도 아님 중 무엇을 검사하는가?
새 컬렉션 구성collect(Collectors.toList())어떤 자료구조로 결과를 모을 것인가?
원소별 최종 동작forEach()각 원소로 어떤 동작을 수행하고 끝낼 것인가?
int[] values = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

long evenCount = Arrays.stream(values)
        .filter(value -> value % 2 == 0)
        .count();
int evenSum = Arrays.stream(values)
        .filter(value -> value % 2 == 0)
        .sum();
int minimum = Arrays.stream(values).min().getAsInt();
double average = Arrays.stream(values).average().getAsDouble();

짝수는 2, 4, 6, 8, 10의 다섯 개이므로 evenCount는 5이고 합은 30이다. 전체의 최솟값은 1, 평균은 (1 + 2 + ··· + 10) ÷ 10 = 55 ÷ 10 = 5.5다. average(), min(), max()는 값이 없을 수도 있는 결과형을 반환하므로 강의 예에서는 값이 있는 배열이라는 전제에서 getAsDouble()이나 getAsInt()로 꺼낸다.

매칭은 양화 조건을 고르고 수집은 결과의 자료구조를 고른다

매칭 연산은 조건을 만족하는 원소의 범위를 묻는다. anyMatch()는 하나라도 만족하는지, allMatch()는 모두 만족하는지, noneMatch()는 어느 원소도 만족하지 않는지 검사한다. “적어도 하나”, “모든”, “하나도 없음”이라는 자연어를 정확히 API에 대응시키는 것이 핵심이다.

int[] values = {1, 2, 3, 4, 5, 6};

boolean hasEven = Arrays.stream(values)
        .anyMatch(value -> value % 2 == 0);       // true
boolean allEven = Arrays.stream(values)
        .allMatch(value -> value % 2 == 0);       // false
boolean noNegative = Arrays.stream(values)
        .noneMatch(value -> value < 0);          // true

collect()는 필터링이나 매핑을 마친 원소를 새로운 컬렉션으로 모은다. Collectors.toList(), toSet(), toMap()의 결과가 collect()의 인수로 들어간다.

List<String> names =
        Arrays.asList("Mina", "Joon", "Minsu", "Sora");

List<String> mNames = names.stream()
        .filter(name -> name.startsWith("M"))
        .collect(Collectors.toList());

Set<String> labelledNames = names.stream()
        .map(name -> name + ":" + name.length())
        .collect(Collectors.toSet());

목록의 순서와 중복을 유지하는 결과가 필요하면 List, 중복 없는 집합이 필요하면 Set, 이름과 나이처럼 키와 값을 연결해야 하면 Map 수집을 선택한다. 수집은 원본 컬렉션을 바꾸는 일이 아니라 처리 결과를 담을 새 컬렉션을 만드는 종료 단계다.

새 파이프라인은 결과에서 거꾸로 설계한다

메서드 체인을 왼쪽부터 무작정 늘리기보다 원하는 결과에서 거꾸로 질문하면 연산 선택이 안정된다.

  1. 최종 결과가 무엇인가? 숫자 하나면 집계, 참·거짓이면 매칭, 자료구조면 수집, 부수 동작이면 forEach()를 검토한다.
  2. 결과 직전에 원소의 형은 무엇이어야 하는가? 숫자 집계가 필요하면 mapToInt() 같은 변환을 배치한다.
  3. 어떤 원소만 남겨야 하는가? 조건 선택은 filter(), 중복 제거는 distinct()로 구분한다.
  4. 원소 하나가 결과 하나인가 여러 개인가? 하나면 map(), 여러 흐름을 합치면 flatMap()을 고른다.
  5. 순서가 결과에 필요한가? 필요하면 자연 정렬 기준이나 비교자를 확인한 뒤 sorted()를 둔다.
  6. 처리 과정을 관찰해야 하는가? peek()를 중간에 두되, 종료 연산의 단축 평가가 관찰 범위를 줄일 수 있음을 예상한다.
  7. 적절한 소스에서 새 스트림을 연다. 한 파이프라인을 종료한 뒤 다른 결과가 필요하면 같은 스트림을 재사용하지 않는다.

직접 구성한 예로, 문자열 목록에서 A로 시작하는 중복 없는 단어를 대문자로 바꾸어 정렬된 목록으로 얻고 싶다고 하자. 결과가 목록이므로 끝은 collect(toList()), 조건은 filter(), 중복 제거는 distinct(), 대문자 변환은 map(), 순서 결정은 sorted()가 된다. 이처럼 결과형부터 정하면 중간 연산의 목적과 순서를 설명할 수 있다.

List<String> result = Arrays.asList(
        "lime", "apple", "apricot", "apple", "pear")
        .stream()
        .filter(word -> word.startsWith("a"))
        .distinct()
        .map(String::toUpperCase)
        .sorted()
        .collect(Collectors.toList());

System.out.println(result); // [APPLE, APRICOT]

핵심 개념 정리

  • 외부 반복은 호출 코드가 순회를 제어하고, 내부 반복은 순회를 데이터 구조나 스트림에 맡기고 처리 동작을 전달한다.
  • 컬렉션·배열·정수 범위·파일은 서로 다른 생성 API를 통해 객체 스트림 또는 기본형 스트림의 소스가 된다.
  • 중간 연산은 새 스트림을 반환해 연결되고, 종료 연산은 파이프라인을 실행해 최종 결과를 만든다.
  • filter()는 조건, distinct()는 동등성, map()은 일대일 변환, flatMap()은 여러 흐름의 평탄화를 담당한다.
  • sorted()는 비교 기준이 필요하고, peek()의 관찰 범위는 뒤의 종료 연산과 단축 평가에 영향을 받는다.
  • 집계는 값 하나, 매칭은 참·거짓, 수집은 새 List·Set·Map을 결과로 만든다.
  • 종료된 스트림은 재사용하지 않고 원본 소스에서 새로운 스트림을 만든다.

스트림 문제를 만나면 결과형 선택 → 필요한 원소형 확인 → 필터링·중복 제거 → 일대일 또는 일대다 매핑 → 정렬·관찰 → 소스 연결 순서로 거꾸로 설계한다. 실행을 추적할 때에는 종료 연산이 있는지 먼저 찾고, 단축 평가가 있다면 모든 원소가 아니라 답이 확정되는 지점까지만 흐른다는 점을 표시하면 긴 메서드 체인도 재현할 수 있다.

예상문제 10선

1. 외부 반복과 내부 반복의 결정적 차이를 가장 정확히 설명한 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②

  • ① 오답: 반복 방식의 구분은 원소 자료형이 아니라 순회 제어의 담당 주체다.
  • ② 정답: for·Iterator는 순회를 명시하고 forEach()는 수행할 동작을 전달한다.
  • ③ 오답: 외부·내부 반복을 순차·병렬과 같은 구분으로 볼 수 없다.
  • ④ 오답: 중간·종료 연산은 스트림 파이프라인의 위치를 구분하는 다른 기준이다.

2. HashMap<String, Integer>의 키·값 쌍을 스트림으로 처리하려 할 때 강의 범위에서 알맞은 출발은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③

  • ① 오답: 행 스트림은 Files.lines(path)에서 만들며 HashMap 메서드가 아니다.
  • ② 오답: Arrays.stream()은 배열을 소스로 사용하며 맵 객체 자체는 배열이 아니다.
  • ③ 정답: 엔트리 집합을 Set으로 얻은 뒤 컬렉션의 stream()을 호출한다.
  • ④ 오답: rangeClosed()는 정수 범위용이며 맵의 키·값 쌍을 제공하지 않는다.

3. Arrays.asList("apple", "banana", "cherry", "Avocado").stream().map(String::toUpperCase).filter(w -> w.startsWith("A")).count()의 결과는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①

  • ① 정답: 대문자 변환 뒤 APPLEAVOCADO 두 원소가 A 조건을 만족한다.
  • ② 오답: 원래 소문자였던 apple도 먼저 대문자로 바뀌므로 함께 통과한다.
  • ③ 오답: BANANACHERRY는 A로 시작하지 않아 둘 다 제외된다.
  • ④ 오답: count()는 원본 수가 아니라 필터 뒤에 남은 원소 수를 센다.

4. 중간 연산만 연결한 코드가 예상한 출력 동작을 하지 않았다. 가장 먼저 확인할 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④

  • ① 오답: 컬렉션은 stream()으로 직접 소스가 될 수 있어 배열 복사가 실행 조건은 아니다.
  • ② 오답: 병렬 여부와 지연된 파이프라인을 시작하는 조건은 서로 다른 문제다.
  • ③ 오답: 중복 제거 횟수보다 종료 연산 부재가 실행되지 않는 직접 원인이다.
  • ④ 정답: 지연 평가 때문에 count()collect() 같은 종료 연산이 있어야 실제 처리가 시작된다.

5. filter()distinct()의 구분으로 옳은 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②

  • ① 오답: 정렬은 sorted(), 변환은 map() 계열의 역할이다.
  • ② 정답: 하나는 전달한 조건식을, 다른 하나는 중복을 판정하는 동등성 기준을 사용한다.
  • ③ 오답: 두 메서드 모두 새 스트림을 반환하는 중간 연산이다.
  • ④ 오답: 필터링과 중복 제거는 객체 스트림과 숫자 스트림에서 모두 사용할 수 있다.

6. 문장 목록의 각 문장을 단어 스트림으로 바꾼 뒤 모든 단어를 하나의 스트림으로 처리하려면 가장 알맞은 연산은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④

  • ① 오답: distinct()는 중복을 제거하지만 여러 내부 스트림을 하나로 합치지 않는다.
  • ② 오답: mapToInt()는 원소를 정수 값으로 바꾸는 일대일 매핑이다.
  • ③ 오답: sorted()는 이미 한 흐름에 있는 원소의 순서를 정한다.
  • ④ 정답: 각 문장에서 생긴 단어 스트림들을 평탄화해 하나의 단어 스트림으로 합친다.

7. 객체 스트림의 sorted()가 인수 없이 자연 순서를 사용하려면 원소가 제공해야 할 비교 기준은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ①

  • ① 정답: 자연 정렬은 원소가 ComparablecompareTo()로 제공한 순서를 따른다.
  • ② 오답: Consumer는 원소를 받아 동작을 수행할 때 사용하며 정렬 순서를 정하지 않는다.
  • ③ 오답: Predicate는 참·거짓 조건을 표현해 필터링이나 매칭에 사용한다.
  • ④ 오답: 수집기는 스트림 결과를 자료구조 등에 모으는 종료 처리에 사용한다.

8. 가격이 차례로 10000, 20000, 14000, 21000, 12000일 때 peek(출력).anyMatch(price -> price > 20000)가 출력하는 원소 수는?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ③

  • ① 오답: 첫 원소 10000은 조건을 만족하지 않으므로 검색이 즉시 끝나지 않는다.
  • ② 오답: 처음 세 가격은 모두 20000보다 크지 않아 네 번째 원소도 검사해야 한다.
  • ③ 정답: 네 번째 가격 21000에서 처음 참이 되어 그 원소까지 출력한 뒤 단축 종료한다.
  • ④ 오답: 답이 확정된 뒤의 12000은 종료 연산이 더 이상 요청하지 않아 peek()를 지나지 않는다.

9. 값이 1, 2, 3, 4인 스트림의 매칭 결과를 바르게 연결한 것은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ②

  • ① 오답: 2와 4가 있으므로 짝수가 하나라도 있다는 조건은 참이다.
  • ② 정답: 짝수 2·4가 존재하고 음수는 하나도 없어 두 결과가 모두 참이다.
  • ③ 오답: 1과 3은 짝수가 아니며 모든 원소가 양수이므로 양수가 하나도 없다는 주장도 거짓이다.
  • ④ 오답: 5보다 큰 값이 없고 2와 4가 홀수가 아니므로 두 결과가 모두 거짓이다.

10. 문자열 목록에서 길이 5 이상인 원소만 골라 길이값으로 바꾼 뒤 합계를 구하는 파이프라인은?

정답입니다.

오답입니다. 답안을 다시 선택해 보세요.

정답 및 해설 보기

정답: ④

  • ① 오답: 정렬·중복 제거·목록 수집은 길이 조건을 적용하거나 합계를 만들지 않는다.
  • ② 오답: 먼저 길이로 바꾸면 조건은 적용할 수 있지만 마지막 count()는 길이의 합이 아니라 원소 수를 반환한다.
  • ③ 오답: 길이는 여러 원소의 스트림이 아니며 anyMatch()의 결과도 합계가 아니라 참·거짓이다.
  • ④ 정답: 문자열 단계에서 조건을 적용하고 길이의 IntStream으로 변환한 뒤 sum()으로 합산한다.

참고 자료와 작성 기준

이 글은 해당 차시 강의자료를 바탕으로 학습 목적에 맞게 재구성한 비공식 학습자료입니다. 스트림 관계망, 지연 평가 추적과 문제 해설은 학습자의 이해를 돕도록 직접 구성하고 검토했습니다.

  • 작성·편집: 올에이클래스 학습연구팀
  • 주요 근거: 한국방송통신대학교 Java프로그래밍 12강 「컬렉션과 스트림」 강의록 :codex-file-citation{path="K1. 컴퓨터과학과/1. 재검토필요/2-1 Java프로그래밍/1. 강의록/Java프로그래밍 강의록 12강.pdf" purpose="source"}
  • 편집 원칙: 올에이클래스 편집 정책
  • 최종 내용 검토: 2026-09-02

댓글