서은파파의 추월차선

Java Stream API에 대해 알아보기 본문

Java

Java Stream API에 대해 알아보기

seoeunpapa 2025. 1. 7. 00:10
728x90

Java Stream API 정리

Java Stream API는 Java 8에 도입된 기능으로, 컬렉션 데이터를 처리하는 데 있어 선언적이고 함수형 프로그래밍 스타일을 제공합니다. 이 포스팅에서는 Java Stream API의 이론, 사용법, 예시, 장단점에 대해 자세히 정리하겠습니다.


1. 이론

1.1 Stream의 정의

Stream은 데이터의 흐름을 추상화한 것으로, 컬렉션, 배열, 또는 I/O 채널 등의 데이터 소스를 처리하는 데 사용됩니다. Stream은 데이터 원본을 변경하지 않고, 필요한 연산을 체이닝 방식으로 선언적으로 기술할 수 있습니다.

1.2 Stream의 특징

  • 선언적 방식: 데이터 처리 과정을 간결하고 가독성 있게 표현.
  • Lazy Evaluation(지연 연산): 최종 연산이 호출되기 전까지 중간 연산은 실행되지 않음.
  • 파이프라인 처리: 중간 연산을 체이닝하여 데이터를 단계적으로 처리.
  • 불변성: 원본 데이터를 변경하지 않음.
  • 병렬 처리 지원: parallelStream()을 사용하여 멀티코어를 활용한 병렬 연산 가능.

1.3 Stream의 구성 요소

  1. 데이터 소스: 컬렉션, 배열, 파일 등.
  2. 중간 연산: 데이터를 변환하거나 필터링하는 작업 (lazy).
  3. 최종 연산: 결과를 생성하거나 Stream 파이프라인을 종료하는 작업.

1.4 Stream의 종류

  • java.util.stream.Stream: 일반 객체 타입.
  • IntStream, LongStream, DoubleStream: 기본형 타입의 Stream.
  • Parallel Stream: 병렬로 처리되는 Stream.

2. 사용법

2.1 Stream 생성

Stream은 다양한 데이터 소스에서 생성할 수 있습니다.

2.1.1 컬렉션에서 생성

List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
Stream<String> parallelStream = list.parallelStream();

2.1.2 배열에서 생성

String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);

2.1.3 Stream.builder() 사용

Stream<String> stream = Stream.<String>builder().add("a").add("b").add("c").build();

2.1.4 Stream.generate() 사용

Stream<Double> stream = Stream.generate(Math::random).limit(10);

2.1.5 Stream.of() 사용

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

2.2 중간 연산

중간 연산은 Stream을 변환하는 작업으로, 체이닝 가능하며 lazy하게 실행됩니다.

2.2.1 map

요소를 변환.

List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.stream().map(n -> n * 2).forEach(System.out::println);

2.2.2 filter

조건에 맞는 요소만 선택.

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().filter(name -> name.startsWith("A")).forEach(System.out::println);

2.2.3 sorted

정렬.

List<Integer> numbers = Arrays.asList(3, 1, 2);
numbers.stream().sorted().forEach(System.out::println);

2.2.4 distinct

중복 제거.

List<Integer> numbers = Arrays.asList(1, 2, 2, 3);
numbers.stream().distinct().forEach(System.out::println);

2.2.5 limit

처리할 요소 개수 제한.

Stream.generate(Math::random).limit(5).forEach(System.out::println);

2.2.6 skip

처음 N개의 요소 건너뜀.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream().skip(2).forEach(System.out::println);

2.3 최종 연산

최종 연산은 Stream 파이프라인을 종료하고 결과를 생성합니다.

2.3.1 forEach

모든 요소에 작업 수행.

List<String> names = Arrays.asList("Alice", "Bob");
names.stream().forEach(System.out::println);

2.3.2 collect

결과를 컬렉션으로 변환.

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

2.3.3 reduce

요소를 하나로 축약.

int sum = Arrays.asList(1, 2, 3).stream().reduce(0, Integer::sum);

2.3.4 count

요소 개수 반환.

long count = Arrays.asList(1, 2, 3).stream().count();

2.3.5 anyMatch / allMatch / noneMatch

조건과 일치하는지 확인.

boolean anyMatch = Arrays.asList(1, 2, 3).stream().anyMatch(n -> n > 2);

3. 예시

3.1 학생 목록 처리

class Student {
    String name;
    int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

    public int getScore() {
        return score;
    }
}

List<Student> students = Arrays.asList(
    new Student("Alice", 85),
    new Student("Bob", 75),
    new Student("Charlie", 95)
);

// 점수가 80 이상인 학생의 이름 목록 출력
students.stream()
        .filter(student -> student.getScore() >= 80)
        .map(Student::getName)
        .forEach(System.out::println);

4. Stream의 구성 요소

(1) 생성 (Creation)

Stream은 다양한 소스에서 생성됩니다:

  • 컬렉션: List, Set, Map 등
  • 배열: Arrays.stream(array)
  • 파일/입출력: Files.lines(path)
  • 범위/숫자: IntStream.range(), Stream.of()
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();

(2) 중간 연산 (Intermediate Operations)

중간 연산은 여러 단계로 구성된 스트림 파이프라인을 생성합니다.

연산 설명 예시

filter() 조건에 맞는 요소를 필터링 stream.filter(x -> x.length() > 2)
map() 요소를 변환 stream.map(String::toUpperCase)
sorted() 요소를 정렬 stream.sorted()
distinct() 중복 제거 stream.distinct()
limit() 개수를 제한 stream.limit(5)
skip() 앞의 n개 요소를 건너뜀 stream.skip(3)

(3) 최종 연산 (Terminal Operations)

최종 연산은 스트림을 소비하고 결과를 반환합니다.

연산 설명 예시

collect() 결과를 컬렉션으로 반환 stream.collect(Collectors.toList())
forEach() 각 요소에 대해 동작 수행 stream.forEach(System.out::println)
reduce() 요소를 축소하여 하나의 값 반환 stream.reduce(0, Integer::sum)
count() 요소 개수 반환 stream.count()
anyMatch() 조건을 만족하는 요소가 있는지 확인 stream.anyMatch(x -> x > 10)

 

5. 장단점

5.1 장점

  1. 코드 간결성: 선언적 스타일로 데이터 처리 로직을 간단히 표현.
  2. 가독성 향상: 데이터 처리 흐름을 직관적으로 이해 가능.
  3. 병렬 처리 지원: parallelStream()으로 병렬 처리가 용이.
  4. 불변성 유지: 원본 데이터를 변경하지 않아 안전한 코드 작성.

5.2 단점

  1. 디버깅 어려움: 체이닝 방식으로 인해 중간 단계 디버깅이 복잡.
  2. 오버헤드: 작은 데이터셋에서는 성능 저하 가능.
  3. 병렬 처리 주의 필요: 병렬 처리 시 올바른 스레드 안전성 보장이 필요.

Java Stream API는 데이터 처리 작업을 간결하고 선언적으로 표현할 수 있는 강력한 도구입니다. 다양한 데이터 소스와의 통합, 지연 연산, 병렬 처리 지원 등 많은 이점을 제공하므로, 적절히 활용하면 생산성과 코드 품질을 크게 향상시킬 수 있습니다.

 

 

 

 

728x90