자바에서는 파일이나 콘솔의 입출력을 직접 다루지 않고, 스트림(stream)이라는 흐름을 통해 다룹니다. 스트림이란 실제의 입력이나 출력이 표현된 데이터의 이상화된 흐름을 의미합니다. 즉, 스트림은 운영체제에 의해 생성되는 가상의 연결 고리를 의미하며, 중간 매개자 역할을 합니다. 자바의 스트림(Stream)은 데이터 처리 작업을 간단하고 효율적으로 수행할 수 있도록 설계된 API로, 특히 Java 8에서 도입된 이후로 데이터의 집합을 처리하는 데 있어 매우 유용한 도구로 자리 잡았습니다.

스트림은 보통 아래와 같은 세 가지 주요 단계로 구성됩니다:
스트림 API에서 자주 사용하는 메서드 몇 가지를 알아보겠습니다:
스트림 생성
List<String> list = Arrays.asList("A", "B", "C");
Stream<String> stream = list.stream();
중간 연산
filter(Predicate)
list.stream().filter(s -> s.startsWith("A"));
map(Function)
list.stream().map(String::toLowerCase);
sorted()
list.stream().sorted();
distinct()
list.stream().distinct();
limit(n) & skip(n)
list.stream().limit(2).skip(1);
최종 연산
collect(Collectors)
List<String> result = list.stream()
.filter(s -> s.startsWith("A"))
.collect(Collectors.toList());
forEach(Consumer)
list.stream().forEach(System.out::println);
reduce(BinaryOperator)
int sum = Arrays.stream(new int[]{1, 2, 3})
.reduce(0, Integer::sum);
한 번 사용한 스트림은 재사용 불가능: 스트림은 일회성으로, 다시 처리하려면 새로운 스트림을 생성해야 합니다.
Stream<String> stream = list.stream();
stream.forEach(System.out::println); // 사용 후 재사용 불가
지연 평가에 유의: 중간 연산은 최종 연산이 호출될 때까지 실행되지 않으므로, 필요 없는 연산을 방지해야 합니다.
병렬 처리 시 주의: parallelStream()은 데이터 구조나 작업 종류에 따라 성능이 저하될 수 있습니다. 스레드 안전성을 확인해야 합니다.
메모리 관리: 무한 스트림(Stream.generate 또는 Stream.iterate)을 사용할 때 메모리 누수에 주의하세요.
Stream.generate(() -> "A").limit(100); // 제한 필수
명확한 가독성: 스트림 체인이 너무 길어지면 가독성이 떨어질 수 있습니다. 적절히 나누거나 주석을 추가하세요.
문자열 리스트에서 특정 조건의 데이터를 필터링하고 변환하는 예제
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("A"))
.map(String::toUpperCase)
.sorted()
.collect(Collectors.toList());
System.out.println(filteredNames); // [ALICE]
숫자 리스트에서 합계 구하기
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream()
.filter(num -> num % 2 == 0) // 짝수만 필터링
.reduce(0, Integer::sum);
System.out.println(sum); // 6