Just Do It

[JAVA] Stream(1)

by 핫도구
반응형
Stream이란?

Stream은 Java 8에서 도입된 함수형 프로그래밍 스타일의 데이터 처리 파이프라인이다. 컬렉션, 배열, I/O 채널 등의 데이터 소스로부터 데이터를 읽어 연속된 요소들을 처리할 수 있게 해주는 추상화된 개념이다.

Stream의 특징
1. 선언적 프로그래밍
- 기존의 명령형 프로그래밍과 달리 무엇을 할 것인지 명시하기 때문에, 무엇을 처리할 지에 집중할 수 있다.
2. 함수형 인터페이스 활용
- 람다 표현식과 메서드 참조를 통해 간결하고 읽기 쉬운 코드를 작성할 수 있다.
3. 지연 연산
- 중간 연산은 실제로 최종 연산이 호출될 때까지 수행되지 않기 때문에, 성능 최적화가 가능하다.
4. 내부반복
- iterator를 사용한 외부 반복과 달리, Stream은 내부적으로 반복을 처리한다.
5. 불변성
- Stream은 원본 데이터를 변경하지 않고 새로운 Stream을 반환한다.
Stream의 장점
1. 생산성 향상
- 복잡한 로직을 간단하게 표현
- 체이닝을 통한 직관적인 데이터 흐름 표현
2. 유지보수성 개선
3. 함수 조합의 유연성
4. 메모리 효율성
5. 타입 안정성

Stream의 단점
1. 디버깅의 어려움
2. 성능 오버헤드
3. 메모리 사용량 증가(불변객체)
4. 예외 처리의 복잡성
5. 잘못된 병렬처리의 성능 저하, 공유 상태 문제
6. 재사용 불가능
public class StreamBasic {
    public static void main(String[] args) {
        // 명령형 vs 선언형 비교 예제
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 기존 방식 (명령형)
        List<Integer> evenDoubled1 = new ArrayList<>();
        for (Integer n : numbers) {
            if (n % 2 == 0) {
                evenDoubled1.add(n * 2);
            }
        }

        // Stream 방식 (선언형)
        List<Integer> evenDoubled2 = numbers.stream()
                .filter(n -> n % 2 == 0)
                .map(n -> n * 2)
                .collect(Collectors.toList());

        System.out.println("명령형 결과: " + evenDoubled1); // [4, 8, 12, 16, 20]
        System.out.println("선언형 결과: " + evenDoubled2); // [4, 8, 12, 16, 20]
    }
}
Stream 생성 방법
public class streamGenExample {
    public static void main(String[] args) {
        // Collection으로부터 생성
        List<String> list = Arrays.asList("a", "b", "c");
        Stream<String> stream1 = list.stream();
        Stream<String> parallelStream = list.parallelStream();
        System.out.println(list); // [a, b, c]

        // 배열로부터 생성
        String[] arr = {"a", "b", "c"};
        Stream<String> stream2 = Arrays.stream(arr);
        Stream<String> stream3 = Stream.of("a", "b", "c");

        // 기본타입 Stream
        IntStream intStream = IntStream.range(1, 5); // 1, 2, 3, 4
        IntStream intStreamClosed = IntStream.rangeClosed(1, 5); // 1, 2, 3, 4, 5
        
        // 무한 Stream
        Stream<Double> randoms = Stream.generate(Math::random);
        Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 2);
        
        // 빈 Stream
        Stream<String> emptyStream = Stream.empty();
        
        // builder 패턴
        Stream<String> builderStream = Stream.<String>builder().add("a").add("b").add("c").build();
    }
}

이런식으로 Collection, Array, 기본타입, 무한, 빈, 빌더 패턴으로 stream을 생성할 수 있다. 생성만 해서 끝이 아니라 이를 통해서 연산을 해야 하는데 바로 연산에 대해 알아보자.

Stream 연산

Stream 연산은 크게 중간연산과 최종연산으로 나뉜다. 중간연산은 Stream을 반환하므로 연속해서 체이닝 할 수 있다. 또한, 지연연산으로 최종연산이 호출되기 전까지 실행되지 않는다. 반면, 최종연산은 Stream을 소비하고 결과를 반환한다. 최종연산이 호출되면 Stream 파이프라인이 실행된다. 그럼 중간연산부터 알아볼 예정인데, 너무 길기 때문에 다음에 연산의 종류에 대해 알아 볼 예정이다.
지연연산이란, 중간연산만으로 실행되는 것이 아닌 최종연산이 호출될 경우에 실행되는 것이다. 또한, 불변객체이기 떄문에 원본에는 영향을 미치지 않는다.

Stream을 언제 사용하고 언제 사용하면 안될까?

Stream을 사용하면 좋은 경우
1. Collection 데이터의 복잡한 변환과 필터링이 필요한 경우
2. 데이터 처리 파이프라인을 명확하게 표현하고 싶은 경우
3. 함수형 프로그래밍 스타일을 선호할 경우
4. 대용량 데이터를 병렬로 처리해야 할 경우
5. 지연 연산의 이점을 활용할 수 있을 경우

Stream을 사용을 피해야 하는 경우
1. 단순한 반복 작업
2. 인덱스가 필요한 경우
3. 결과를 여러번 사용해야 하는 경우
4. 성능이 극도로 중요한 경우


이처럼 Stream API는 Java에서 함수형 프로그래밍 패러다임을 도입한 강력한 도구이다. 다음에는 Stream의 다양한 연산에 대해 알아 볼 예정으로 filter, map, reduce와 같은 핵심 연산부터 공급연산까지 알아 볼 예정이다.

반응형

'JAVA > MID' 카테고리의 다른 글

[JAVA] Stream(2)  (1) 2025.09.26
[JAVA] 람다  (0) 2025.09.20
[JAVA] Optional  (0) 2025.09.17
[JAVA] 제네릭(Generic)  (0) 2025.09.12
[JAVA] 예외처리(2)  (0) 2025.09.11

블로그의 정보

AquaMan

핫도구

활동하기