본문 바로가기
Java/java

Java Stream을 활용한 데이터 처리와 활용

by 노마드 산코디 2023. 10. 3.
728x90

안녕하세요. 오늘은 자바 Stream에 대한 내용을 주제로 글을 쓰려고 합니다. Stream은 데이터를 효율적으로 처리하고 다룰 수 있는 강력한 도구로, 함수형 프로그래밍의 개념을 도입하여 코드를 더 간결하고 가독성 있게 만들어 줍니다. 이 글에서는 Stream의 기초적인 기능을 알아보고, 실제 활용 사례와 함께 정리해 보도록 하겠습니다.

 

 

 


1. Stream이란?

자바의 Stream은 자바 프로그래밍 언어에서 데이터 컬렉션을 다루는데 사용되는 강력한 API(응용 프로그래밍 인터페이스)입니다. Stream은 배열, 컬렉션, I/O 작업 등 다양한 데이터 소스에서 데이터를 처리하고 변환하는 데 사용됩니다.


특징

  • 스트림 생성
    스트림은 컬렉션, 배열 또는 I/O 채널로부터 생성됩니다. 주로 컬렉션에 대한 스트림을 생성하여 컬렉션의 요소를 처리하는 데 사용됩니다.


  • 함수형 프로그래밍
    스트림은 함수형 프로그래밍 패러다임을 적용한 자바의 일부입니다. 이것은 스트림을 사용하여 데이터를 처리하고 변환할 때 람다 표현식과 함수형 인터페이스를 활용하는 것을 의미합니다.


  • 중간 및 최종 연산
    스트림 연산은 중간 및 최종 연산으로 나뉩니다. 중간 연산은 스트림을 변환하고 필터링하는 연산이며, 최종 연산은 스트림 처리를 시작하고 결과를 반환하는 연산입니다.


  • 지연 평가
    스트림은 지연 평가(Lazy Evaluation)를 통해 최적화됩니다. 이것은 필요한 경우에만 요소를 계산하고 처리하는 것을 의미하며, 불필요한 계산을 피할 수 있습니다.


  • 파이프라인
    스트림 연산은 파이프라인으로 연결됩니다. 이것은 여러 중간 연산과 최종 연산을 연속적으로 적용할 수 있게 해줍니다.


    병렬 처리
    스트림을 사용하면 멀티코어 프로세서에서 데이터를 병렬로 처리할 수 있어 성능을 향상시킬 수 있습니다. Parallel Stream을 사용하여 간단하게 병렬 처리를 활성화할 수 있습니다.



Java Stream은 데이터 처리 작업을 간소화하고 성능을 최적화하는 강력한 도구로, 코드를 더 간결하게 작성할 수 있도록 도와줍니다.




 


2. Stream 생성

Stream을 생성하는 방법은 주로 컬렉션, 배열 또는 I/O 채널로부터 스트림을 생성하는 것이 일반적입니다. 아래는 다양한 데이터 소스로부터 Stream을 생성하는 예시와 방법입니다.

 

1. 컬렉션으로부터 생성
컬렉션은 스트림으로 쉽게 변환할 수 있습니다. stream() 메서드를 호출하면 컬렉션의 모든 요소를 다룰 수 있는 스트림이 생성됩니다.

List<String> myList = Arrays.asList("Java", "Python", "C++", "JavaScript");
Stream<String> stream = myList.stream();




2. 배열로부터 생성
배열에서 스트림을 생성하려면 Arrays.stream() 메서드를 사용합니다.

String[] myArray = {"Apple", "Banana", "Cherry", "Date"};
Stream<String> stream = Arrays.stream(myArray);




3. 스트림 팩토리 메서드 사용
Stream.of() 메서드를 사용하여 값을 나열하여 스트림을 생성할 수 있습니다.

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);




4. 파일로부터 생성
파일에서 스트림을 생성하려면 Files.lines() 메서드를 사용합니다.

try (Stream<String> lines = Files.lines(Paths.get("example.txt"), Charset.defaultCharset())) {
    // 파일의 각 줄을 다루는 작업
} catch (IOException e) {
    e.printStackTrace();
}




5. 랜덤한 요소로 생성
Stream.generate() 메서드를 사용하여 무한한 스트림을 생성할 수 있습니다. 여기서는 람다 표현식을 사용하여 값을 생성합니다.

Stream<Double> randomStream = Stream.generate(() -> Math.random());




이러한 방법을 사용하여 다양한 데이터 소스로부터 Stream을 생성할 수 있으며, 이후에 스트림을 사용하여 데이터를 처리하거나 변환할 수 있습니다.








3. Stream 연산

자바 Stream에서 데이터를 연산하려면 중간 연산(intermediate operation)과 최종 연산(terminal operation)을 사용합니다. 중간 연산은 스트림을 변환하거나 필터링하고, 최종 연산은 스트림에서 최종 결과를 얻는 역할을 합니다. 아래에 자주 사용되는 스트림 연산을 정리해보겠습니다.

중간 연산 (Intermediate Operations)

1. filter (Predicate)
주어진 조건에 맞는 요소만을 걸러내는 연산입니다.

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> evenNumbers = numbers.filter(n -> n % 2 == 0);



2. map (Function)
각 요소를 다른 값으로 변환하는 연산입니다.

Stream<String> words = Stream.of("apple", "banana", "cherry");
Stream<Integer> wordLengths = words.map(String::length);



3. flatMap (Function)
스트림의 각 요소를 다른 스트림으로 매핑하고, 그 결과를 하나의 스트림으로 평탄화하는 연산입니다.

Stream<List<Integer>> nestedList = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4));
Stream<Integer> flatStream = nestedList.flatMap(List::stream);



4. distinct()
중복 요소를 제거하는 연산입니다.

Stream<String> distinctWords = Stream.of("apple", "banana", "cherry", "apple").distinct();



5. sorted()
요소를 정렬하는 연산입니다.

Stream<Integer> numbers = Stream.of(5, 3, 1, 4, 2);
Stream<Integer> sortedNumbers = numbers.sorted();




최종 연산 (Terminal Operations)

1. forEach(Consumer)
각 요소에 대해 주어진 동작을 수행하는 연산입니다.

Stream<String> words = Stream.of("apple", "banana", "cherry");
words.forEach(System.out::println);



2. collect (Collector)
스트림의 요소를 수집하여 컬렉션 또는 다른 결과 형식으로 반환하는 연산입니다.

List<String> wordList = Stream.of("apple", "banana", "cherry").collect(Collectors.toList());



3. count()
스트림의 요소 개수를 반환하는 연산입니다.

long count = Stream.of(1, 2, 3, 4, 5).count();



4. anyMatch (Predicate)
주어진 조건에 맞는 요소가 스트림에 하나라도 있는지 확인하는 연산입니다.

boolean anyMatch = Stream.of(1, 2, 3, 4, 5).anyMatch(n -> n > 3); // true



5. allMatch (Predicate)
모든 요소가 주어진 조건을 만족하는지 확인하는 연산입니다.

boolean allMatch = Stream.of(1, 2, 3, 4, 5).allMatch(n -> n > 0); // true



6. noneMatch (Predicate)
모든 요소가 주어진 조건을 만족하지 않는지 확인하는 연산입니다.

boolean noneMatch = Stream.of(1, 2, 3, 4, 5).noneMatch(n -> n < 0); // true



7. reduce()
스트림의 모든 요소를 하나의 값으로 축소하는 연산입니다. 최댓값, 최솟값 또는 합을 구할 때 사용됩니다.

Optional<Integer> sum = Stream.of(1, 2, 3, 4, 5).reduce((x, y) -> x + y);









4. Stream 연산 예제

Stream을 사용하여 간단한 연산 예제를 정리해보겠습니다. 아래 예제에서는 리스트에서 숫자를 필터링하고 변환하며, 최종적으로 결과를 얻는 방법입니다.


예시 코드

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 1. 중간 연산: 짝수만 필터링
        List<Integer> evenNumbers = numbers.stream()
                .filter(n -> n % 2 == 0)
                .collect(Collectors.toList());

        System.out.println("짝수만 필터링 결과: " + evenNumbers);

        // 2. 중간 연산: 제곱 수 계산
        List<Integer> squaredNumbers = numbers.stream()
                .map(n -> n * n)
                .collect(Collectors.toList());

        System.out.println("제곱 수 계산 결과: " + squaredNumbers);

        // 3. 중간 연산: 중복 제거
        List<Integer> distinctNumbers = numbers.stream()
                .distinct()
                .collect(Collectors.toList());

        System.out.println("중복 제거 결과: " + distinctNumbers);

        // 4. 최종 연산: 요소 합계 계산
        int sum = numbers.stream()
                .reduce(0, (x, y) -> x + y);

        System.out.println("요소 합계: " + sum);
    }
}

 

 

  • filter()를 사용하여 짝수만 필터링합니다.

  • map()을 사용하여 각 요소를 제곱 수로 변환합니다.

  • distinct()를 사용하여 중복 요소를 제거합니다.

  • reduce()를 사용하여 모든 요소의 합계를 계산합니다.

 

Stream API를 활용하면 데이터 처리 작업을 간결하고 가독성 있게 수행할 수 있습니다.







5. Stream 사용시 주의사항



1. 일회용
Stream은 한 번만 사용할 수 있습니다. 한 번 사용한 Stream은 재사용할 수 없습니다. 필요한 경우 중간 결과물을 컬렉션 등에 저장하고 재활용하는 방법을 고려해야 합니다.

2. 지연 연산
Stream 연산은 지연 연산(lazy evaluation)을 사용합니다. 이는 Stream에서 데이터를 처리하기 전까지 실제 연산이 수행되지 않음을 의미합니다. 최종 연산을 호출하기 전까지 중간 연산은 실제 실행되지 않으므로 주의해야 합니다.

3. 최종 연산 필요
중간 연산만 수행하면 아무런 결과가 생성되지 않습니다. 최종 연산을 호출하여 Stream 처리를 활성화해야 합니다. 최종 연산을 호출하지 않으면 중간 연산은 실행되지 않습니다.

4. 병렬 Stream 사용 시 주의
병렬 Stream은 멀티코어 CPU에서 동시에 처리할 수 있도록 지원합니다. 그러나 스레드 간의 동기화 및 데이터 무결성을 보장하기 위해 주의가 필요합니다.

5. 제한된 Stream 크기
무한한 Stream을 생성하지 않도록 주의해야 합니다. 무한한 Stream을 다룰 때는 제한된 크기로 변환하거나 조건을 활용하여 종료 조건을 명시해야 합니다.

6. 자원 해제
파일이나 네트워크 연결과 같은 자원을 다룰 때는 try-with-resources 블록을 사용하여 자원을 명시적으로 해제해야 합니다.

7. 에러 처리
Stream 연산 중에 발생할 수 있는 예외를 처리하는 방법을 고려해야 합니다. 예외 처리는 try-catch 블록을 활용하여 적절하게 수행해야 합니다.

8. 성능 고려
Stream 연산은 간결하고 가독성이 높지만, 불필요한 중간 연산을 사용하거나 비효율적인 방식으로 스트림을 활용하면 성능 문제가 발생할 수 있습니다. 연산의 효율성을 고려하여 코드를 작성해야 합니다.


Stream은 데이터 처리를 간소화하고 가독성을 높이는 데 도움이 되지만, 올바르게 사용하기 위해서는 위와 같은 주의사항을 숙지하는 것이 중요합니다.








최종 정리

자바 Stream은 데이터 처리와 조작을 간편하게 할 수 있는 강력한 도구로, 자바 프로그래밍에서 더 나은 코드를 작성하고 효율적인 데이터 처리를 가능하게 합니다. 이러한 스트림을 활용하면 복잡한 데이터 조작도 더 쉽게 처리할 수 있으며, 자바 언어의 강력한 기능 중 하나입니다. Stream을 다루는 데 필요한 기본적인 내용을 다루었으며, 앞으로 Stream을 더 깊게 탐구하여 프로그래밍 작업을 보다 효율적으로 수행할 수 있기를 바랍니다.

감사합니다.

 

 

 

728x90
반응형