'자바 stream api'에 해당되는 글 1건

자바의 Stream API는 Java 8부터 도입된 기능으로, 컬렉션(Collection) 데이터를 처리할 때 선언형(Declarative) 방식으로 간결하고 효율적으로 작업할 수 있도록 도와주는 기능이다.

쉽게 말하면, 데이터를 필터링하고, 변형하고, 집계하는 일련의 작업을 파이프라인처럼 연결해서 처리할 수 있는 도구라고 보면 된다.

 

Stream API는 단독으로 뭔가 하는 게 아니라
"데이터 → Stream → 연산들"이라는 전체 흐름 속에서 사용된다는 점이 핵심이다.

 


1. Stream 만드는 방법 (데이터 → Stream)

Stream API를 쓰기 위해서는 반드시 "스트림 객체"를 만들어야 한다.
즉, Stream은 “데이터 소스”로부터 시작해서, 그 위에 중간 연산과 최종 연산을 쌓는 구조이다.

가장 많이 쓰는 순서부터 한 번 작성해 보겠다.

1. 컬렉션(Collection)으로부터

List<String> list = List.of("A", "B", "C");
Stream<String> stream = list.stream();
  • .stream() → 순차 스트림
  • .parallelStream() → 병렬 스트림 (멀티코어 처리)

list.stream()은  실제로 Stream<String> stream = list.stream(); 이렇게 변수에 담는 걸 생략한 것 뿐이다.

 


2. 배열로부터

String[] arr = {"A", "B", "C"};
Stream<String> stream = Arrays.stream(arr);

 

  • Collections를 상속 받지 않은 Arrays(배열)은 Arrays.stream()을 이용하여 stream객체를 생성 가능하다.

3. Stream 클래스의 정적 메서드 사용

Stream<String> stream = Stream.of("A", "B", "C");
  • .of() → 가변 인자를 스트림으로
  • .generate(Supplier) → 무한 스트림 (ex: 랜덤값 반복 생성)
  • .iterate(seed, UnaryOperator) → 무한 반복 스트림

예:

Stream<Integer> nums = Stream.iterate(1, n -> n + 1); // 1, 2, 3, ...

Stream<String> stream = Stream.of("a", "b", "c"); 

Stream<int[]> intArrayStream = Stream.of(new int[]{1, 2, 3});

 


4. 파일 등 외부 자원

Stream<String> lines = Files.lines(Paths.get("file.txt"));

 


5. Stream.builder() 을 활용하여 수동으로 만들 때

Stream.Builder<String> builder = Stream.builder();

builder.add("apple");
builder.add("banana");
builder.add("cherry");

Stream<String> stream = builder.build();

 

간단한 버전 :

Stream<String> stream = Stream.<String>builder()
    .add("apple")
    .add("banana")
    .add("cherry")
    .build();

 

주의할 점 :

  • .build()를 호출한 후에는 .add()를 더 이상 사용할 수 없음 (불변 스트림 생성)
  • 너무 많은 요소를 직접 add()로 넣는 건 귀찮음 → 이럴 땐 List.stream()이나 Stream.of()가 더 나음

Stream 만들기 요약 정리

방법 설명
list.stream() 컬렉션에서 스트림 생성
Arrays.stream() 배열에서 스트림 생성
Stream.of(...) 가변 인자 스트림 생성
Stream.builder() 수동으로 add() 해서 스트림 생성
Stream.generate() Supplier 기반 무한 스트림
Stream.iterate() 초기값과 함수 기반 반복 스트림

 

 

 


 

 

Stream 객체를 만들었다면 이걸 활용해야 한다.

 

만들어지 Stream 객체를 사용하기 위해서는 2단계 과정을 더 거쳐야한다. 바로 중간 연산최종 연산이다.

 

 

1. 중간 연산 (Intermediate Operations)

스트림을 변형시키고 연결만 하고, 실제 실행은 최종 연산이 호출될 때까지 지연됨 (lazy evaluation).

 

연산 설명 예시
filter(Predicate) 조건에 맞는 요소만 통과 filter(x -> x > 10)
map(Function) 요소를 다른 형태로 변환 map(String::length)
flatMap(Function) 중첩된 스트림을 평탄화 flatMap(list -> list.stream())
sorted() 기본 정렬 sorted()
sorted(Comparator) 사용자 정의 정렬 sorted(Comparator.reverseOrder())
distinct() 중복 제거 distinct()
limit(n) 앞에서 n개만 선택 limit(5)
skip(n) 앞에서 n개 건너뜀 skip(3)
peek(Consumer) 디버깅용: 요소를 들여다봄 peek(System.out::println)

 

2. 최종 연산 (Terminal Operations)

스트림 연산을 실행하고 결과를 반환함.
여기서 연산이 실제로 수행됨.

 

연산 설명 반환형
forEach(Consumer) 각 요소에 대해 작업 void
collect(Collector) 요소들을 수집 (리스트, 집합 등으로) List, Set 등
reduce() 누적해서 하나의 값으로 합침 Optional<T>
count() 요소 수 계산 long
anyMatch(), allMatch(), noneMatch() 조건에 대한 논리값 boolean
findFirst(), findAny() 요소 하나 반환 Optional<T>
toArray() 배열로 변환 Object[] 또는 T[]

 

중간 연산의 경우 lazy하여 최종연산이 실행되기 전까지 아무 일도 일어나지 않는다. 

 

최종 연산은 한 번만 실행 가능하며, 중간 연산은 여러번 사용해도 된다.

 

3. 쉽게 기억하는 비유!

  • Stream = 컨베이어 벨트
  • 중간 연산 = 벨트 위에서 필터링, 변형, 정렬하는 장치들
  • 최종 연산 = 완성품을 포장하거나 소비하는 마지막 과정
  • 포장하고 나면, 벨트는 멈추고 재사용 불가

예시 :

List<String> names = List.of("Alice", "Bob", "Charlie", "David");

long count = names.stream()
    .filter(name -> name.length() > 3)  // 중간 연산
    .map(String::toUpperCase)          // 중간 연산
    .distinct()                        // 중간 연산
    .count();                          // 최종 연산

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

 

 


 

 

Stream API를 활용하려면 람다(Lambda)식도 알아야한다.

참 귀찮은 일이지만 다음 포스트에 한 번 람다식에 관해 써보겠다.

블로그 이미지

Ahan

책, 영화, 게임! 인생의 활력 요소가 되는 취미들을 하자!

,