자바 스트림(Stream) API 활용 방법과 팁

자바 스트림(Stream) API 활용 방법과 팁

Java Stream API

자바 8부터는 새로운 기능인 스트림(Stream) API가 추가되었습니다. 스트림 API는 데이터 처리에 관한 새로운 접근법을 제공하여 컬렉션, 배열 등의 데이터를 처리하기 위한 간단하고 선언적인 방법을 제공합니다. 스트림 API는 데이터를 처리하기 위한 다양한 기능을 제공하며, 람다식과 함께 사용하면 코드의 가독성과 유지보수성이 향상됩니다.

이번 글에서는 자바 스트림 API의 소개부터 사용 방법, 최적화 방법 및 람다식과 스트림 API의 함께 사용에 대해 알아보겠습니다.

자바 스트림 API 소개

스트림(Stream)은 자바 8에서 추가된 컬렉션, 배열 등의 데이터를 처리하기 위한 새로운 기능입니다. 스트림 API는 데이터를 처리하는 과정을 간단하고 선언적으로 구현할 수 있도록 해줍니다. 스트림 API는 다음과 같은 특징을 가지고 있습니다.

  • 스트림은 원본 데이터를 변경하지 않습니다.
  • 스트림은 중간 처리 과정과 최종 처리 과정으로 나뉩니다.
  • 스트림은 지연 평가(Lazy Evaluation)를 지원합니다. 즉, 필요한 시점에만 처리합니다.
  • 스트림은 병렬 처리(Parallel Processing)를 지원합니다.

스트림 API는 다양한 메소드를 제공합니다. 이를 이용하여 스트림을 생성하고, 중간 처리 과정과 최종 처리 과정을 수행할 수 있습니다. 스트림 API는 다음과 같이 사용됩니다.

List numbers = Arrays.asList(1, 2, 3, 4, 5);

int sum = numbers.stream()
        .filter(n -> n % 2 == 0)
        .mapToInt(Integer::intValue)
        .sum();

System.out.println(sum);

위 코드는 리스트에서 짝수만 필터링하여 합을 구하는 예제입니다. 스트림 API는 stream() 메소드를 호출하여 스트림을 생성하고, filter() 메소드를 호출하여 중간 처리 과정을 수행합니다. mapToInt() 메소드를 호출하여 스트림의 요소를 int형으로 매핑하고, sum() 메소드를 호출하여 최종 처리 과정을 수행합니다.

스트림 API 사용 방법

스트림 API는 다양한 메소드를 제공합니다. 이를 이용하여 스트림을 생성하고, 중간 처리 과정과 최종 처리 과정을 수행할 수 있습니다.

스트림 생성

스트림은 다양한 데이터 소스를 이용하여 생성할 수 있습니다. 다음은 스트림을 생성하는 예제 코드입니다.

List list = Arrays.asList("apple", "banana", "orange");

// Collection으로부터 스트림 생성
Stream stream1 = list.stream();

// 배열로부터 스트림 생성
int[] array = new int[]{1, 2, 3, 4, 5};
IntStream stream2 = Arrays.stream(array);

// 파일로부터 스트림 생성
Path path = Paths.get("file.txt");
Stream stream3 = Files.lines(path);

위 코드에서 list 컬렉션, array 배열, file.txt 파일로부터 각각 스트림을 생성하는 방법을 보여줍니다.

중간 처리 과정

스트림의 중간 처리 과정은 여러 개의 중간 처리 메소드를 이용하여 처리할 수 있습니다. 중간 처리 메소드는 필터링, 매핑, 정렬 등의 기능을 제공합니다. 다음은 중간 처리 메소드의 예제 코드입니다.

List numbers = Arrays.asList(1, 2, 3, 4, 5);

// 필터링
Stream evenStream = numbers.stream().filter(n -> n % 2 == 0);

// 매핑
IntStream intStream = numbers.stream().mapToInt(Integer::intValue);

// 정렬
Stream sortedStream = numbers.stream().sorted();

위 코드에서 filter() 메소드는 짝수만 필터링하는 기능을 제공하고, mapToInt() 메소드는 int형으로 매핑하는 기능을 제공합니다. sorted() 메소드는 스트림을 정렬하는 기능을 제공합니다.

최종 처리 과정

스트림의 최종 처리 과정은 스트림의 최종 결과를 반환하는 메소드를 호출하여 처리할 수 있습니다. 최종 처리 메소드는 count(), collect(), forEach() 등의 기능을 제공합니다. 다음은 최종 처리 메소드의 예제 코드입니다.

List numbers = Arrays.asList(1, 2, 3, 4, 5);

// 요소 개수 반환
long count = numbers.stream().count();

// 리스트로 변환
List list = numbers.stream().collect(Collectors.toList());

// 출력
numbers.stream().forEach(System.out::println);

위 코드에서 count() 메소드는 스트림의 요소 개수를 반환하는 기능을 제공하고, collect() 메소드는 스트림을 리스트로 변환하는 기능을 제공합니다. forEach() 메소드는 스트림의 요소를 출력하는 기능을 제공합니다.

스트림 API 팁과 최적화

스트림 API를 사용할 때 주의해야 할 몇 가지 팁과 최적화 방법이 있습니다.

람다식과 스트림 API 함께 사용하기

람다식과 스트림 API를 함께 사용하면 코드의 가독성과 유지보수성이 향상됩니다. 이를 이용하여 스트림에서 필요한 요소를 추출하거나, 요소를 변환하거나, 요소를 필터링하는 등의 작업을 수행할 수 있습니다. 다음은 람다식과 스트림 API를 함께 사용하는 예제 코드입니다.

List list = Arrays.asList("apple", "banana", "orange");

// 필터링
List newList = list.stream()
        .filter(s -> s.startsWith("a"))
        .collect(Collectors.toList());

// 변환
List intList = list.stream()
        .map(s -> s.length())
        .collect(Collectors.toList());

// 필터링 + 변환
List newIntList = list.stream()
        .filter(s -> s.startsWith("a"))
        .map(s -> s.length())
        .collect(Collectors.toList());

위 코드에서 filter() 메소드는 "a"로 시작하는 문자열만 필터링하는 기능을 제공하고, map() 메소드는 문자열의 길이를 반환하는 기능을 제공합니다. collect() 메소드는 스트림을 리스트로 변환하는 기능을 제공합니다.

병렬 처리

스트림 API는 병렬 처리를 지원합니다. 병렬 처리를 이용하여 작업 속도를 높일 수 있지만, 적절한 상황에서만 사용해야 합니다. 병렬 처리를 이용하면 스트림의 요소를 분할하여 병렬적으로 처리하므로, 작업 속도를 높일 수 있습니다. 다음은 병렬 처리 예제 코드입니다.

List list = Arrays.asList(1, 2, 3, 4, 5);

// 직렬 처리
long start1 = System.currentTimeMillis();
int sum1 = list.stream().mapToInt(Integer::intValue).sum();
long end1 = System.currentTimeMillis();
System.out.println("sum1 : " + sum1 + " time : " + (end1 - start1) + "ms");

// 병렬 처리
long start2 = System.currentTimeMillis();
int sum2 = list.parallelStream().mapToInt(Integer::intValue).sum();
long end2 = System.currentTimeMillis();
System.out.println("sum2 : " + sum2 + " time : " + (end2 - start2) + "ms");

위 코드에서 stream() 메소드는 직렬 처리를 수행하고, parallelStream() 메소드는 병렬 처리를 수행합니다.

스트림 재사용

스트림은 재사용이 불가능합니다. 한 번 사용한 스트림은 다시 사용할 수 없습니다. 따라서, 여러 개의 스트림을 생성하여 사용해야 합니다. 다음은 스트림을 재사용하지 않는 예제 코드입니다.

List list = Arrays.asList(1, 2, 3, 4, 5);

// 스트림을 두 번 사용
int sum1 = list.stream().mapToInt(Integer::intValue).sum();
int sum2 = list.stream().mapToInt(Integer::intValue).sum();

위 코드에서 stream() 메소드를 두 번 호출하여 스트림을 생성하면, 첫 번째 스트림을 사용한 후에 두 번째 스트림을 사용할 수 없습니다. 따라서, 스트림을 재사용하지 않고, 여러 개의 스트림을 생성하여 사용해야 합니다.

스트림 최적화

스트림 API는 다양한 기능을 제공하지만, 성능상의 이유로 최적화가 필요할 수 있습니다. 스트림을 최적화하는 방법에는 다음과 같은 것들이 있습니다.

  • findFirst() 대신 findAny() 메소드 사용
  • parallel() 메소드 사용 시 처리 속도 측정
  • filter() 메소드 사용 시 대소문자 구분 주의
  • map() 메소드 사용 시 반환 값 타입 고려

람다식과 스트림 API 함께 사용하기

람다식과 스트림 API를 함께 사용하면 코드의 가독성과 유지보수성이 향상됩니다. 이를 이용하여 스트림에서 필요한 요소를 추출하거나, 요소를 변환하거나, 요소를 필터링하는 등의 작업을 수행할 수 있습니다.

List numbers = Arrays.asList(1, 2, 3, 4, 5);

// 필터링
List evenList = numbers.stream()
        .filter(n -> n % 2 == 0)
        .collect(Collectors.toList());

// 매핑
List doubleList = numbers.stream()
        .map(n -> n * 2)
        .collect(Collectors.toList());

// 최대값 구하기
int max = numbers.stream()
        .max(Integer::compare)
        .get();

위 코드에서 filter() 메소드는 짝수만 필터링하는 기능을 제공하고, map() 메소드는 요소를 2배로 매핑하는 기능을 제공합니다. max() 메소드는 스트림에서 최대값을 구하는 기능을 제공합니다.

결론

이번 글에서는 자바 스트림 API에 대해 소개하고, 사용 방법 및 팁과 최적화 방법에 대해 알아보았습니다. 스트림 API는 데이터 처리에 새로운 접근법을 제공하여 코드의 가독성과 유지보수성을 향상시켜주며, 병렬 처리를 지원하여 작업 속도를 높일 수 있습니다. 따라서, 스트림 API를 적극적으로 활용하여 개발 생산성을 향상시키기를 권장합니다.