[Java] 자바 스트림(Stream) 개념 + 예제 총정리 📚

2025. 6. 3. 02:11·공부/☕Java

✅ 1. 외부반복과 내부반복

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// 외부 반복: for-each문
for (String name : names) {
    System.out.println(name);
}
// 내부 반복: Stream + forEach
names.stream().forEach(name -> System.out.println(name));

 

(1) 외부반복

  • 개발자가 루프 구조를 직접 작성해서 원소를 하나씩 처리하는 방식을 의미한다.
  • 쉽게 말해, for문 while문 Iterator 같은 반복문을 직접 사용해서 어떻게 반복할지 직접 명시해서 제어하는 경우이다.

(2) 내부반복

  • 반복의 제어를 라이브러리나 프레임워크에게 맡기고, 개발자는 무엇을 할지만 전달하는 방식을 의미한다.
  • 대표적으로는 자바의 Stream API가 내부 반복을 사용하고 있음.
  • 즉, 어떻게 반복할지는 메서드를 통해 호출하고, 어떤걸 반복할지만 넘겨주면 되는 방식이다.
  • 다양한 반복을 코드로 미리 만들어서 메서드로 제공을 해주기 때문에 구현없이 편리하게 호출만으로 가능
  • 즉, 다양한 메서드를 통해 직접 구현하지 않고 구현 된 반복문 코드를 호출 할 수 있다.

✅ 2. Stream API

// Stream
public interface Stream<T> extends BaseStream<T, Stream<T>> {
    Stream<T> filter(Predicate<? super T> predicate);
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    void forEach(Consumer<? super T> action);
    ...
}
// Collection - List, Set, Map ...
public interface Collection<E> extends Iterable<E> {
    default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }
}
  • Java 8부터 도입된 기능으로 반복, 필터링, 매핑, 정렬, 집계 등을 선언적으로 수행할 수 있게 도와주는 API이다.
  • 쉽게말해, 컬렉션에 포함이 되어있는게 아닌 JRE에 포함 된 자바 API이다.
  • 보통 함수형 스타일 즉, 람다식 처리를 지원하며, 대표적으로는 컬렉션 데이터를 다룰 때 많이 사용 됨.
  • Stream API는 인터페이스로 제공이 되며, 해당 클래스를 내부적으로 구현한 클래스 또한 제공을 해준다.
  • 대표적으로 내부 구현 클래스로는 ReferencePipeline 클래스를 제공해준다.
  • 해당 StreamSupport 클래스의 stream() 메서드를 통해 구현 클래스인 ReferencePipeline을 Stream으로 반환해줌.
  • 컬렉션 내부에서는 StreamSupport 클래스의 stream() 메서드를 통해 클래스를 이용함.
  • 즉, 컬렉션이 Stream 인터페이스를 구현하는게 아닌, 이미 구현 된 Stream 구현 클래스를 호출해서 이용함.
  • 핵심은 컬렉션 및 배열 같은 데이터 소스를 선언형으로 처리할 수 있도록 도와주는 도구(API)이다.

✅ 3. 기본자료형 스트림

// IntStream 사용 예제
IntStream.of(1, 2, 3).sum(); // 6
IntStream.range(1, 4).forEach(System.out::println); // 1, 2, 3
IntStream.rangeClosed(1, 3).average(); // OptionalDouble[2.0]

// 배열과 IntStream 사용 예제
int[] numbers = {1, 2, 3, 4, 5};

int sum = IntStream.of(numbers).sum();          // 합계: 15
double avg = IntStream.of(numbers).average().orElse(0); // 평균: 3.0

int[] numbers = {5, 1, 3, 9, 7};

IntStream.of(numbers)             // 배열을 스트림으로
         .sorted()               // 정렬
         .forEach(System.out::println);
  • IntStream, LongStream, DoubleStream 인터페이스를 제공해줌.
  • 기본형 int, long, double형의 원소로 이루어진 데이터를 다루기 위한 스트림이다.
  • 기본 Stream은 객체 타입만 다루기 때문에 int, long, dobule 같은 기본형은 오토박싱/언박싱이 필요함.
  • 이 과정에서 오토박싱, 언박싱은 성능 저하의 원인이 되므로, 기본형 전용 Stream을 제공하는 것임.

✅ 4. 배열 스트림

// 기본형 배열
int[] nums = {1, 2, 3, 4, 5};
int sum = Arrays.stream(nums).sum(); // 15

int[] intArr = { 1, 2, 3 };
IntStream intStream = Arrays.stream(intArray);
intStream.forEach(item -> System.out.println(item));

// 참조(객체) 배열
String[] words = {"a", "bb", "ccc"};
long count = Arrays.stream(words)
                   .filter(w -> w.length() >= 2)
                   .count(); // 2 ("bb", "ccc")
                   
String[] strArr = {"홍길동","이순신","김유신"};
Stream<String> strem = Arrays.stream(strArray);
strem.forEach(item -> System.out.println(item));
  • 배열을 Stream으로 바꾸는 표준적인 방법을 의미한다.
  • 컬렉션 클래스들은 .stream()을 바로 쓸 수 있지만, 배열은 Arrays.stream()을 써야 이용이 가능함.
  • 즉, 배열을 스트림의 편리한 기능을 활용하기 위해 스트림으로 만드는 경우임.

✅ 5. 파일 스트림

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

fileStream.forEach(line -> System.out.println(line));
fileStream.close();
  • File.lines(path)을 사용하여 텍스트 파일로부터 행 단위 문자열로 구성된 스트림을 생성할 수 있음.
  • 이처럼 컬렉션 뿐 아니라 다양한 곳에서 Stream API를 활용하고 있음.

✅ 6. 컬렉션 스트림

List<String> list = List.of("apple", "banana", "cherry");

list.stream()
    .filter(s -> s.startsWith("b"))
    .forEach(System.out::println); // "banana"
  • Collection 인터페이스에서 stream()과 parallelStream() 메서드를 제공을 해줌.
  • 해당 메서드를 통해 Stream API로 컬렉션 데이터를 Stream의 메서드로 처리 할 수 있게 됨.
  • 즉, 컬렉션을 스트림으로 바꿔 반복문 없이 map, filter, collect 같은 선언형 처리가 가능해짐.
  • parallelStream()은 병럴처리가 가능한 스트림을 리턴해줌.
  • 즉, stream()은 동기식 + 순차 처리, parallelStrem()은 동기식 + 병렬처리(멀티스레드) 으로 처리를 해줌.
  • 쉽게말해, 일반 스트림은 단일 스레드로 동기적으로 처리를 해줌.
  • parallelStrem()은 동기적으로 처리를 해주지만, 멀티스레드로 병렬로 처리를 해줌.

(1) 컬렉션 - 일반 스트림 & 병렬 스트림

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

// 일반 스트림 사용 : stream()
Stream<Integer> stream = set.stream();
stream.forEach(item -> System.out.println(item)); // 결과: 0123456789 순차적으로 출력

// 병렬 스트림 사용 : parallelStream()
Stream<Integer> stream = set.parallelStrema();
stream.forEach(item -> System.out.println(item)); // 결과: 스케줄러의 의해 랜덤처럼 출력됨.
// 즉, 1204356789 이런식으로 출력됨 각 숫자는 하나의 스레드가 맡아서 처리를 함.
  • 병렬 스트림은 parallelStream() 메서드를 이용하여 처리를 할 수 있음.
  • 해당 메서드를 이용하면 스트림 메서드를 통해 필터나 다양한 메서드를 이용하게 되면 병렬로 처리가 됨.
  • 즉, 병렬처리 여러 스레드를 만들어서 병렬로 처리를 하는 것을 의미하며, 숫자가 랜덤처럼 보일 수 있음.
  • 이유는, cpu의 코어 수에 맞게 처리가 되며, 운영체제의 스케줄러의 의해 처리가 되므로 랜덤처럼 보임.
  • 즉, 스레드를 여러개 만들어서 처리를 하게 되면 CPU 코어를 활용 할 수 있다는 느낌임.

✅ 7. 스트림 파이프라인과 중간 & 최종 연산

  • 스트림 파이프라인은 컬렉션, 배열, 또는 파일로부터 생성된 스트림에 어떤 처리를 수행하고 새로운 스트림이 만들어지며, 계속해서 이러한 처리 과정을 반복하는 것을 의미하며, 메서드 체이닝을 사용하여 파이프라인을 구축할 수 있다.
  • 해당 파이프라인에서 제공해주는 메서드들은 중간연산과 최종연산으로 나뉘며, 중간 연산만으로는 아무 일도 일어나지 않는다. 또한, 최종 연산이 호출될 때 중간 연산들이 함께 실행이 되며 이걸 지연 연산이라고 한다.
List<String> words = List.of("apple","banana", "cherry", "Avocado");

long count = words.stream() // 원본 스트림
        .map(String::toUpperCase) // 중간연산
        .filter(word -> word.startsWith("A")) // 중간연산 - A로 시작하는 요소만
        .count(); // 최종연산 - 최종 리스트의 요소 개수를 리턴해줌.
System.out.println(count); // 2가 출력됨

 

(1) 중간연산

  • 해당 스트림을 가공하거나 필터링을 하는 단계이다.
  • 항상 새로운 스트림을 반환하며, 연속적으로 체이닝을 하며 메서드를 호출하며 필터, 정렬 등을 적용 할 수 있음.
  • 원본 스트림에서 데이터를 변환하거나 필터링 등으로 새로운 스트림을 생성하는 연산
  • 중간연산에서 생성된 스트림은 다음 연산으로 연결되어 파이프라인을 형성함
  • filter(), map(), sorted() 등의 연산

(2) 최종연산(종료연산)

  • 스트림 처리의 마지막 단계를 의미하며, 메서드로 제공이 됨.
  • 중간 연산이 정의한 작업을 실제로 실행하는 역할을 가지며, 최종 연산이 수행되면 이후 스트림은 재사용 불가됨.
  • collect(), forEach(), count(), anyMatch() ... 등이 있음.
  • 중간연산을 거친 스트림에 대해 최종적인 결과를 만들거나 동작을 수행하는 부분임.
  • 스트림 파이프라인에서 마지막에 한 번만 수행이 됨.

✅ 8. 중간연산

(1) 필터링

  • 컬렉션 원소들 중에서 중복을 제거하거나 특정 조건을 만족하는 원소만 추출하여 새로운 스트림을 생성하는 작업
  • distinct(): 중복 제거를 해주는 메서드이며, 중복된 데이터를 하나로 만들고 나머지는 걸러주는 역할을 함.
  • filter(): 조건을 통해 배열의 값을 걸러내기 위해 사용하는 메서드 ( 함수형 인터페이스로 작용 )

(2) 매핑

  • 원소들을 다른 원소로 변환하여 새로운 스트림을 생성하는 작업을 의미한다.
  • 스트림의 요소를 다른 형태로 변환하는 작업을 의미한다.
  • 즉, 기존 스트림의 각 요소에 함수를 적용해서 새로운 스트림을 생성하는 과정을 의미한다.

[1] map()

  • 하나의 입력을 하나의 출력으로 매핑 즉, 요소를 일대일로 변환할 때 사용이 됨.
List<String> names = List.of("jason", "emily", "michael");

List<String> upperNames = names.stream()
    .map(String::toUpperCase)  // 각 요소를 대문자로 변환
    .collect(Collectors.toList());

System.out.println(upperNames); // [JASON, EMILY, MICHAEL]

 

[2] flatMap()

  • 하나의 입력을 여러 출력으로 펼침 즉, 요소 하나를 여러 요소로 펼쳐서 스트림을 생성할 때 사용이 됨.
List<String> words = List.of("Java", "Stream");

List<String> characters = words.stream()
    .flatMap(word -> Arrays.stream(word.split(""))) // 각 단어를 글자로 분해해서 펼침
    .collect(Collectors.toList());

System.out.println(characters); // [J, a, v, a, S, t, r, e, a, m]

 

[3] 객체 필드 매핑도 가능

  • 스트림의 각 요소를 함수에 적용해서 다른 객체로 변환할 때도 사용이 될 수 있음.
class User {
    private String name;
    public User(String name) { this.name = name; }
    public String getName() { return name; }
}

List<User> users = List.of(new User("Alice"), new User("Bob"));

List<String> names = users.stream()
    .map(User::getName)   // User 객체에서 이름만 추출
    .collect(Collectors.toList());

System.out.println(names); // [Alice, Bob]

// 해당 함수 인터페이스 사용이 됨.
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);   
}

 

(2) 정렬

  • 스트림의 원소들을 오름차순 또는 내림차순으로 정렬하여 새로운 스트림을 생성하는 작업을 의미한다.
  • Stream<T> 에서 sorted() 메소드를 사용해서 정렬을 할 수 있음.
  • 정렬이 되는 클래스에서는 Comparable 인터페이스를 구현해 compareTo() 메서드를 오버라이딩을 해야 사용이 가능함.
List<String> names = List.of("Charlie", "Alice", "Bob");

List<String> sorted = names.stream()
    .sorted() // 기본 정렬: 알파벳 순 -> String이 Comparable을 구현하고 있어서 가능함.
    .collect(Collectors.toList());

System.out.println(sorted); // [Alice, Bob, Charlie]
  • 이 코드는 사용이 가능하며, 이유는 String 클래스가 Comparable을 구현하고 있기에 가능함.
class User implements Comparable<User> {
    private String name;
    private int age;

    // 기본 정렬 기준 정의 (age 기준 오름차순)
    @Override
    public int compareTo(User other) {
        return Integer.compare(this.age, other.age);
    }
}

List<User> users = List.of(
    new User("Alice", 30),
    new User("Bob", 25),
    new User("Charlie", 35)
);

List<User> sortedUsers = users.stream()
    .sorted() // compareTo 기준(age 기준)으로 정렬
    .collect(Collectors.toList());

System.out.println(sortedUsers); // [Bob(25), Alice(30), Charlie(35)]
  • 해당 코드는 사용자 정의로 직접 인터페이스를 구현해서 사용이 가능함
  • 즉, sorted() 매서드로 정렬을 하기 위해서는 해당 인터페이스를 구현해야함.
  • 하지만, 직접 구현하지 않고도 할 수 있는 방법이 있음.
List<User> sortedByName = users.stream()
    .sorted(Comparator.comparing(User::getName)) // 이름 기준 오름차순
    .collect(Collectors.toList());

List<User> sortedByAgeDesc = users.stream()
    .sorted(Comparator.comparing(User::getAge).reversed()) // 나이 내림차순
    .collect(Collectors.toList());
  • Cinoarator을 사용하는 경우 인터페이스를 구현하지 않아도 된다. 외부에서 정렬 기준을 제공하는 방식이다.

✅ 9. 종료연산

  • 중간처리를 거친 스트림에 대해 집계나 결과 출력 등의 최종처리를 수행하는 것을 최종연산이라고 한다.
  • 집계(합계, 평균, 최대, 최소 등), 매칭, 수집 등의 작업을 수행함
  • 스트림 파이프라인의 마지막 단계에서 사용이되며, 스트림을 다시 사용할 수 없게 만든다.
  • 종료연산이 수행되기 전까지는 스트림 파이프라인이 실제로 실행이 되지는 않는다 ( 지연 연산 )
  • 지연 평가를 통해 스트림 파이프라인을 효율적으로 구성하며 필요한 결과를 얻는다.

(1) 루핑

  • 스트림의 각 요소를 순회하면서 어떤 동작을 수행하는 것을 의미한다.
  • 쉽게 말해, 스트림의 forEach() 같은 메서드를 의미한다. 해당 메서드는 요소의 전체를 순회하는 작업을 제공해줌
  • peek() 메소드는 각 요소를 순회하면서 주어진 동작(람다식)을 수행하는 중간 연산으로 디버깅이나 로깅에 자주 이용됨.
  • forEach() : 종료연산 / peek() : 중간연산

(2) 매칭

[1] allMatch (Predicate)

List<Integer> numbers = List.of(2, 4, 6, 8);
boolean allEven = numbers.stream().allMatch(n -> n % 2 == 0); // true
  • 스트림의 모든 요소가 조건을 만족하면 true, 하나라도 만족하지 않으면 false
  • 만약 어떤 요소가 조건을 만족하지 않으면 즉시 false 를 반환하고 탐색을 종료한다.

[2] anyMatch (Predicate)

List<Integer> numbers = List.of(1, 3, 5, 6);
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0); // true (6이 짝수)
  • 스트림의 요소 중 하나라도 조건을 만족하면 true, 아니면 false
  • 조건을 만족하는 요소를 찾으면 즉시 true 를 반환하고 탐색을 종료한다.

[3] noneMatch (Predicate)

List<Integer> numbers = List.of(1, 3, 5);
boolean noEven = numbers.stream().noneMatch(n -> n % 2 == 0); // true
  • 스트림의 어떤 요소도 조건을 만족하지 않으면 true, 하나라도 만족하면 false
  • 조건을 만족하는 요소를 찾으면 즉시 false 를 반환하고 탐색을 종료한다.

(3) 수집

// List
List<String> names = List.of("홍길동", "임꺽정", "장보고");
List<String> result = names.stream()
    .filter(name -> name.length() >= 3)
    .collect(Collectors.toList());
// Set
Set<String> upperNames = names.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toSet());
  • 스트림의 요소들을 다른 형태로 모아서 변환하는 최종 연산을 의미한다.
  • 가장 대표적으로 Collectiors 유틸리티 클래스와 함께 사용이 된다.
  • 즉, 마지막에 가공한 데이터를 "원하는 자료형 박스(List,Set,Map 등)에 담는 것을 의미한다.
  • 쉽게 말해, 스트림에서 필터 등에 따라 변화 된 데이터 모음을 객체로 담아서 전달을 해주기 위해 사용을 함.

'공부 > ☕Java' 카테고리의 다른 글

[Java] 프록시 패턴, 정적 프록시, 동적 프록시: Spring 사례 중심 설명  (1) 2025.07.09
[Java] 멀티 스레드 프로그래밍  (2) 2025.06.04
[Java] 컬렉션 프레임워크 정리: List, Set, Map 인터페이스와 대표 구현체 총정리  (0) 2025.05.30
[Java] java.nio 사용법 | Path, Files, FileChannel 예제 모음  (3) 2025.05.30
[Java] java.io 패키지 입출력 스트림 쉽게 이해하기  (2) 2025.05.28
'공부/☕Java' 카테고리의 다른 글
  • [Java] 프록시 패턴, 정적 프록시, 동적 프록시: Spring 사례 중심 설명
  • [Java] 멀티 스레드 프로그래밍
  • [Java] 컬렉션 프레임워크 정리: List, Set, Map 인터페이스와 대표 구현체 총정리
  • [Java] java.nio 사용법 | Path, Files, FileChannel 예제 모음
junbin2
junbin2
java.lang.NullPointerException
  • junbin2
    bin's Development Diary
    junbin2
  • 전체
    오늘
    어제
    • 전체보기 (233)
      • 방송통신대학교 (87)
        • ⚙️컴퓨터의 이해 (11)
        • 💻컴퓨터과학 개론 (15)
        • 🔢자료구조 (14)
        • 🧬알고리즘 (10)
        • ⚙️운영체제 (14)
        • 🕸️이산수학 (11)
        • 🌍유비쿼터스 컴퓨팅 (11)
        • 🖥️컴퓨터과학과 (1)
      • 공부 (72)
        • 📚백엔드 공부 (6)
        • ☕Java (23)
        • 🌳Spring (13)
        • ⚙️C (12)
        • ⚡Python (15)
        • JavaScript (1)
        • 🛢️Database (0)
        • Algorithm Problem Solving (2)
      • 네트워크 (7)
        • 📜HTTP (7)
      • 스파르타코딩클럽 (64)
      • 정보 (2)
      • 정리가 필요한 글 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • GitHub
  • 공지사항

  • 인기 글

  • 태그

    Java
    컴퓨터의 이해
    자료구조
    이산수학
    자바
    Python
    배열
    그래프
    방송대
    spring
    방통대
    C언어
    파이썬
    운영체제
    방송통신대학교
    컴퓨터과학과
    알고리즘
    함수
    컴퓨터과학 개론
    유비쿼터스
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
junbin2
[Java] 자바 스트림(Stream) 개념 + 예제 총정리 📚
상단으로

티스토리툴바