Java 8부터 도입된 Stream API는 프로그래밍 스타일을 완전히 바꿔놓았다고 해도 과언이 아니다. 처음에는 그냥 반복문을 대체하는 기능 정도로만 생각했는데, 쓰면 쓸수록 '이거 꽤 강력한데?'라는 느낌이 든다. 특히, 함수형 프로그래밍 스타일과 맞물려서 사용하면 코드가 훨씬 깔끔해지고 유지보수가 편리해진다.
ㅁStream API: 컬렉션(List, Set 등)의 요소들을 선언형(declarative) 방식으로 처리할 수 있게 도와주는 기능
기존의 for-each 문을 사용한 코드보다 훨씬 간결하게 데이터를 필터링하거나 변환할 수 있다
(기존의 반복문 방식)
public static void withoutStream() {
String[] arrayOfStrings = {"A", "B", "C"};
for (String name : arrayOfStrings) {
System.out.println(name);
}
}
위 코드에서는 for 루프를 사용해서 직접 filteredNames 리스트를 관리해야 한다. 사실 복잡한 로직이 추가되면 가독성이 떨어지고, 실수할 가능성도 높아진다.
(Stream API 활용 방식)
public static void withStream() {
String[] arrayOfStrings = {"A", "B", "C"};
Arrays.stream(arrayOfStrings).forEach(System.out::println);
}
불필요한 for 문이 사라지고, "이 코드가 어떤 동작을 하는지"
- Arrays.stream(arrayOfStrings) → 배열을 Stream(데이터 흐름)으로 변환
- .forEach(System.out::println) → 각 요소를 System.out.println()으로 출력
이런 스타일이 바로 함수형 프로그래밍(Functional Programming) 스타일이다!
조금 더 깊게 들어가보자
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
numbers.stream()
.map(n -> {
System.out.println("Mapping: " + n);
return n * 2;
})
.filter(n -> {
System.out.println("Filtering: " + n);
return n > 5;
})
.forEach(n -> System.out.printf("## forEach: %d %n%n", n));
}
}
스트림은 한 번에 모든 데이터를 처리하는 것이 아니라, 요소 단위로 체이닝을 실행한다
즉, map → filter → forEach를 한 요소씩 실행하고, 다음 요소로 넘어가는 방식이다!
※ Lazy Evaluation(게으른 연산): 불필요한 연산을 최소화하여 성능을 최적화하는 방식
함수형 프로그래밍이란?
함수형 프로그래밍은 "어떻게(How)"가 아니라 "무엇을(What)" 할지를 표현하는 방식이다. 전통적인 명령형 프로그래밍에서는 변수를 변경하면서 원하는 결과를 만들어내지만, 함수형 프로그래밍에서는 상태 변경을 최소화하고 데이터를 변환하여 새로운 값을 반환하는 방식으로 동작한다.
🟢 순수 함수(Pure Function)
순수 함수란 입력값이 같으면 항상 같은 결과를 반환하는 함수를 의미한다. 그리고 외부 상태를 변경하지 않는다.
public static int square(int x) {
return x * x;
}
이 함수는 어떤 환경에서 호출하든 같은 x 값이 들어오면 항상 같은 결과를 반환한다. 반대로, 외부 변수를 변경하는 함수는 순수 함수가 아니다.
🔵 불변성(Immutability)
함수형 프로그래밍에서는 데이터를 변경하는 대신, 새로운 데이터를 생성하여 반환하는 방식을 선호한다. Java의 Stream API는 이런 개념을 기반으로 동작한다.
List<String> upperCaseNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
원본 names 리스트는 변경되지 않고, 새로운 리스트가 생성된다. 이렇게 하면 버그를 줄이고 멀티스레드 환경에서도 안정적인 코드를 작성할 수 있다.
<개인적인 Stream API를 사용하면서 느낀 점>
Stream API를 처음 접했을 때는 "이거 그냥 for-each 대체 아닌가?" 싶었지만, 실제로 적용해 보니코드가 간결해지고 유지보수가 훨씬 편해졌다. 요약하면
- 코드가 깔끔하다: for 문을 사용하면 여러 줄이 필요하지만, Stream API를 활용하면 코드 줄을 줄일 수 있다
- 가독성이 좋다: 데이터를 어떻게 변환하는지 한눈에 들어온다. map(), filter(), collect() 같은 메서드 이름만 봐도 어떤 동작을 하는지 직관적으로 알 수 있다
- 병렬 처리에 강하다: .parallelStream()을 활용하면 멀티코어 환경에서도 쉽게 병렬 처리할 수 있다
하지만 단점도 있다. Stream을 남용하면 가독성이 떨어질 수도 있다.
list.stream()
.filter(x -> x % 2 == 0)
.map(x -> x * 2)
.sorted()
.distinct()
.limit(5)
.collect(Collectors.toList());
위처럼 메서드 체이닝이 길어지면 처음 보는 사람은 "이게 뭘 하는 코드지?" 싶을 수 있다.
따라서 적절한 주석과 함께 사용하는 것이 중요하다!
[정리]
Java의 Stream API는 단순히 for-each 문을 대체하는 것이 아니라, 함수형 프로그래밍 스타일을 Java에서 활용할 수 있도록 도와주는 강력한 도구이다. 처음에는 어색할 수 있지만, 점점 익숙해지면 "이걸 왜 안 썼지?" 싶은 순간이 온다. 특히, 불변성을 유지하고, 데이터 흐름을 선언적으로 표현할 수 있다는 점에서 현대적인 프로그래밍 스타일과 잘 맞아떨어진다.
하지만 무조건 Stream API를 쓰는 것이 좋은 것은 아니다. 단순한 반복문이라면 굳이 Stream을 사용할 필요는 없고, 가독성이 떨어질 수도 있다. 중요한 것은 언제, 어디서 사용하면 가장 효율적인지 감을 익히는 것이다. 결국 적절한 도구를 적절한 곳에서 사용하는 것이 가장 좋은 코드 작성법이라는 생각이 든다.
앞으로도 Stream API를 적극 활용해야겠다!
[Java API] 함수형 프로그래밍(java.util.function 패키지)
https://claremont.tistory.com/entry/Java-Stream-API%EC%99%80-%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D [Java] Stream API와 함수형 프로그래밍Java 8부터 도입된 Stream API는 프로그래밍 스타일을 완전
claremont.tistory.com
'Language > Java' 카테고리의 다른 글
[Java API] 함수형 프로그래밍 익숙해지기(java.util.function 패키지) (1) | 2025.03.17 |
---|---|
[Java] 버전 수정 기여(김영한의 실전 자바 - 중급 1편) (1) | 2025.03.14 |
[Java API] 정렬 메서드(sort) 종류와 차이점 (2) | 2024.10.29 |
[Java] 컬렉션 프레임워크 선택 매뉴얼 (0) | 2024.10.27 |
[Java API] java.util.Collections 메서드 (0) | 2024.10.27 |