[Lecture/Java] 12강 - 컬렉션과 스트림

김찬미·2025년 5월 29일

12강 - 컬렉션과 스트림

  • forEach() 메소드
  • 스트림
  • 스트림 파이프라인
  • 중간연산
  • 종료연산

forEach() 메소드

컬렉션이나 배열의 원소를 다룰 때, 원소의 반복 처리를 프로그램에서 명시적으로 제어하는 방식

  • 원소를 프로그램에서 선언된 변수로 복사한 후 작업 처리

  • for, 향상된 for, while, do-while, Iterator 등을 이용한 반복 작업 → 외부 반복


내부 반복과 forEach() 메소드

원소의 반복 처리를 컬렉션 또는 스트림과 같은 데이터 구조 내부에서 반복 처리하는 방식

  • 프로그램에서 스트림 API를 이용하여 반복 처리 위임
    → 처리 코드만 람다식으로 제공
// 예: 컬렉션의 forEach() 메소드 이용, 매개변수로 람다식을 지정
// forEach() 메소드는 함수형 인터페이스인 Consumer 객체를 인자로 받음

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

//내부 반복
names.forEach(item -> System.out.println("내부 반복: " + item));

👍 내부 반복의 장점

  • 코드가 간결해짐
  • 가독성이 좋아짐
  • 병렬 처리 가능
  • 성능 최적화에 유리

✅ 스트림

컬렉션과 배열과 같은 데이터 소스로부터 만들어지는 원소의 시퀀스를 표현하며, 간결하고 효율적 처리 방법을 제공하는 인터페이스

  • 내부 반복함수형 프로그래밍 방식 지원

  • 멀티코어 CPU를 활용한 병렬 처리 지원

  • 관련 클래스와 인터페이스는 java.util.stream 패키지에 있음

  • 다양한 집합체 연산을 지원함 (중간처리 연산과 최종처리 연산)

filter(), map(), sorted(), count(), collect(), anyMatch() ...

🔸스트림의 특성

  • 데이터 원본으로부터 스트림 생성 → 원본 데이터 변경❌

  • 스트림은 일회용

  • 스트림 연산을 파이프라인 형태로 연결할 수 있음

  • 지연 평가를 통해 연산을 최적화


숫자와 스트림

IntStream, LongStream, DoubleStream 인터페이스

기본형 int, long, double형의 원소로 이루어진 데이터를 다루기 위한 스트림

🔹주요 기능

static IntStream range(int, int)
static IntStream rangeClosed(int, int)

static IntStream of(int... values)

sum(), average(), min(), max(), ...
filter(), map(), reduce(), ...
System.out.println(IntStream.rangeClosed(1, 100).sum());
System.out.println(IntStream.rangeClosed(1, 100).average().getAsDouble());
System.out.println(IntStream.rangeClosed(1, 100).min().getAsInt());
System.out.println(IntStream.rangeClosed(1, 100).max().getAsInt());

배열과 스트림

Arrays.stream()를 사용하여 배열로부터 스트림 생성 가능

🔹Array 클래스에서 제공되는 메소드

static DoubleStream stream(double[] array)
static IntStream stream(int[] array)
static <T> Stream<T> stream(T[])
// 원소가 객체인 배열로부터 스트림 생성
// Stream은 제네릭 인터페이스

// 문자열 배열 스트림
String[] strArray = { "홍길동", "이순신", "김유신" };
Stream<String> strStream = Arrays.stream(strArray);
strStream.forEach(item -> System.out.println(item));

// 정수 배열 스트림
int[] intArray = { 1, 2, 3 };
IntStream intStream = Arrays.stream(intArray);
intStream.forEach(item -> System.out.println(item));

파일과 스트림

File.lines(Path)를 사용하여 텍스트 파일로부터 행 단위 문자열로 구성된 스트림 생성 가능

Path path = Paths.get("c:\\data\\data.txt");
Stream<String> fileStream = Files.lines(path);

fileStream.forEach(line -> System.out.println(line));
fileStream.close();

컬렉션과 스트림

1) Collection 유형의 객체인 경우

Collection 인터페이스에서 stream()parallelStream() 메소드가 디폴트 메소드로 제공

  • parallelStream()병렬처리가 가능한 스트림을 리턴

Hashset, ArraySet, LinkedList 객체 등으로부터 스트림을 생성할 때 사용

2) HashMap 객체의 경우

  1. entrySet()을 사용하여 Set 유형의 객체를 얻음
  2. stream 또는 parallelStream 메소드를 사용하여 스트림 생성

✅ 스트림 파이프라인

컬렉션, 배열, 또는 파일로부터 생성된 스트림에 어떤 처리를 수행하고 새로운 스트림이 만들어지며, 계속해서 이러한 처리 과정을 반복하는 것

  • 메소드 체이닝을 사용하여 파이프라인을 구축할 수 있음

🔸중간연산(또는 중간처리)

  • 원본 스트림에서 데이터를 변환하거나 필터링 등으로 새로운 스트림을 생성하는 연산
  • 중간연산에서 생성된 스트림은 다음 연산으로 연결되어 파이프라인을 형성함
  • filter(), map(), sorted() 등의 연산

🔸종료연산(또는 최종연산)

  • 중간연산을 거친 스트림에 대해 최종적인 결과를 만들거나 동작을 수행하는 부분

  • 스트림 파이프라인에서 마지막에 한 번만 수행됨

  • forEach(), collect(), count(), anyMatch, reduce()

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가 출력됨 

메소드 체이닝의 활용

메소드의 연속적 호출


✅ 중간연산

컬렉션 원소들 중에서 중복을 제거하거나 특정 조건을 만족하는 원소만 추출하여 새로운 스트림을 생성하는 작업

  • Stream<T>, IntStream, LongStream, DoubleStream에서 필터링 메소드는 중간 연산이므로 같은 스트림을 리턴함
  • 중복 제거에는 distinct() 메소드를 사용
    • 원소가 객체인 경우: hashCode()의 리턴값 동일
    • equals()로 비교: true가 반환되면 중복으로 판단
  • 조건을 통한 걸러 내기: filter() 메소드 사용

💡 filter() 메소드

  • filter() 메소드의 매개 변수는 함수형 인터페이스
  • true 또는 false를 리턴하는 람다식을 매개변수로 전달

🔄 매핑

스트림의 원소를 다른 형태로 변환하는 작업
하나의 스트림을 다른 스트림으로 가공하는 과정

🔹주요 메소드

메서드설명
map()일반 객체 스트림을 다른 객체 스트림으로 변환
mapToInt()객체 스트림을 **기본형 IntStream**으로 변환
flatMap() / flatMapToInt()요소를 스트림으로 변환 후, 여러 스트림을 하나로 합침 (평탄화)
asDoubleStream() / asLongStream()IntStream다른 기본형 스트림으로 변환
boxed()기본형 스트림을 객체 스트림 (예: IntStreamStream<Integer>) 으로 변환

📌 참고

  • map1:1 변환,
  • flatMap1\:N 변환 후 평탄화에 적합함
  • boxed()는 기본형을 객체로 감싸는 과정 (오토박싱)

정렬

스트림의 원소들을 오름차순 또는 내림차순으로 정렬하여 새로운 스트림을 생성하는 작업

  • Stream<T>에서 sorted() 메소드 사용

    • 정렬된 새로운 스트림 리턴

    • T 유형의 객체(원소)는 Comparable이어야 하며, 크기 비교에 compareTo() 메소드 사용

    • 기본형에 대응되는 포장 클래스는 모두 Comparable

💡 Comparable<T>

  • Comparable<T>"자연 순서(Natural Ordering)"를 정의하기 위해 사용하는 인터페이스이다.
  • 예: 문자열의 사전 순, 숫자의 오름차순 등

루핑

스트림의 원소들을 하나씩 순회하면서 반복적으로 처리하고 새로운 스트림을 생성하는 작업

peek 메소드

각 요소를 순회하며 주어진 동작(람다식)을 수행하는 중간 연산으로, 디버깅이나 로깅에 자주 사용됨

  • 새로운 스트림을 반환, 최종 연산과 함께 사용
  • 스트림 파이프라인에서 peek()anyMatch()를 조합하면 처리되는 원소를, 조건식에서 처음 true가 되는 원소까지로 제한하게 됨

💡 forEach() vs anyMatch()

  • forEach() 메소드는 각 요소를 반복하며 주어진 조건(람다식)을 수행하는 종료 연산
  • anyMatch() 메소드는 종료 연산으로 주어진 조건(람다식)을 만족하는 요소를 찾으면 검색(순회)를 중단

✅ 종료연산

중간 처리를 거친 스트림에 대해 집계나 결과 출력 등의 최종 처리를 수행하는 최종연산

  • 집계(합계, 평균, 최대, 최소 등), 매칭, 수집 등의 작업 수행

  • 스트림 파이프라인의 마지막 단계에서 사용되며, 스트림을 다시 사용할 수 없게 함

    • 종료 연산이 수행되기 전까지는 스트림 파이프라인이 실제로 실행되지 않음 lazy evaluation

    • 지연 평가를 통해 스트림 파이프라인을 효율적으로 구성
      → 필요한 결과 얻음


집계

원수들의 개수, 합계, 평균, 최댓값, 최솟값 등을 구하는 최종처리

count()
sum()
average()
max()
min()

IntStream에서 average(), max(), min()는 OptionalInt | OptionalDouble값을 리턴
getAsInt() 또는 getAsDouble()로 값을 변환해야 함


매칭

스트림의 원소들이 특정 조건을 만족하는지를 확인하는 최종처리

  • anyMatch(): 하나라도 만족 → true
  • allMatch(): 모두 만족해야 → true
  • noneMatch(): 아무도 만족하지 않으면 → true
List<Integer> nums = List.of(1, 2, 3, 4, 5);

nums.stream().anyMatch(n -> n > 4); // true (5가 조건 만족)
nums.stream().allMatch(n -> n < 10); // true (모두 10보다 작음)
nums.stream().noneMatch(n -> n < 0); // true (음수 없음)

수집

스트림의 원소들을 필터링 또는 매핑한 후 최종적으로 새로운 컬렉션(List, Set, Map)을 생성하는 종료 연산

  • Collectors 클래스는 스트림의 요소들을 수집하는 데 다양한 static 유틸리티 메소드 제공
    → 이것의 결과가 collect()의 인자로 사용됨
// 남성 회원만 필터링하여 리스트로 수집
List<Member> male_members = members.stream()
    .filter(member -> member.getGender().equals("남"))
    .collect(Collectors.toList());

// 남성 회원 출력
male_members.stream()
    .forEach(member -> System.out.println(member));

// 여성 회원만 필터링하여 Set으로 수집
Set<Member> female_members2 = members.stream()
    .filter(member -> member.getGender().equals("여"))
    .collect(Collectors.toSet());

// 여성 회원 출력
female_members2.stream()
    .forEach(member -> System.out.println(member));

// 여성 회원의 이름과 나이를 Map으로 수집
Map<String, Integer> ages = female_members2.stream()
    .collect(Collectors.toMap(
        member -> member.getName(),
        member -> member.getAge()
    ));

// 이름-나이 맵 출력
System.out.println(ages);

profile
백엔드 지망 학부생

0개의 댓글