함수형 프로그래밍
순수 함수와 불변성을 기반으로, 상태 변경과 부수 효과를 최소화하여 프로그래밍하는 패러다임입니다.
핵심 개념
| 개념 | 설명 |
|---|---|
| 순수 함수 | 같은 입력 → 항상 같은 출력, 부수 효과 없음 |
| 불변성 | 데이터를 변경하지 않고 새로운 데이터 반환 |
| 1급 함수 | 함수를 변수에 할당, 인자로 전달, 반환 가능 |
| 고차 함수 | 함수를 인자로 받거나 반환하는 함수 |
순수 함수
// X 순수 함수 아님 (외부 상태 변경)
int count = 0;
void increment() {
count++; // 부수 효과 발생
}
// O 순수 함수 (같은 입력 → 같은 출력, 외부 영향 없음)
int add(int a, int b) {
return a + b;
}
불변성
// X 불변성 위반 (원본 변경)
List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
list.add(4); // 원본 변경
// O 불변성 유지 (새 리스트 반환)
List<Integer> list = List.of(1, 2, 3);
List<Integer> newList = Stream.concat(list.stream(), Stream.of(4))
.collect(Collectors.toList());
1급 함수 (함수형 인터페이스)
// Java에서는 함수형 인터페이스로 함수를 1급 객체처럼 사용
Function<Integer, Integer> double_ = x -> x * 2;
Function<Integer, Integer> addOne = x -> x + 1;
// 변수에 할당
double_.apply(5); // 10
// 인자로 전달
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
numbers.stream()
.map(double_) // 함수를 인자로 전달
.collect(Collectors.toList()); // [2, 4, 6, 8, 10]
고차 함수 (Stream API)
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
// map - 변환
List<Integer> doubled = numbers.stream()
.map(x -> x * 2)
.collect(Collectors.toList()); // [2, 4, 6, 8, 10]
// filter - 필터링
List<Integer> evens = numbers.stream()
.filter(x -> x % 2 == 0)
.collect(Collectors.toList()); // [2, 4]
// reduce - 집계
int sum = numbers.stream()
.reduce(0, Integer::sum); // 15
함수 합성
Function<Integer, Integer> double_ = x -> x * 2;
Function<Integer, Integer> addOne = x -> x + 1;
// andThen: double_ 실행 후 addOne 실행
Function<Integer, Integer> doubleThenAdd = double_.andThen(addOne);
doubleThenAdd.apply(3); // 7 (3 * 2 + 1)
// compose: addOne 실행 후 double_ 실행
Function<Integer, Integer> addThenDouble = double_.compose(addOne);
addThenDouble.apply(3); // 8 ((3 + 1) * 2)
커링
// Java에서 커링 구현
Function<Integer, Function<Integer, Integer>> add = a -> b -> a + b;
Function<Integer, Integer> add5 = add.apply(5); // 부분 적용
add5.apply(3); // 8
add5.apply(10); // 15
명령형 vs 함수형
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
// 명령형 - 어떻게(How) 할지 작성
List<Integer> result = new ArrayList<>();
for (int n : numbers) {
if (n % 2 == 0) {
result.add(n * 2);
}
}
// 함수형 - 무엇을(What) 할지 작성
List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * 2)
.collect(Collectors.toList());
장단점
| 장점 | 단점 |
|---|---|
| 테스트 용이 (순수 함수) | 학습 곡선 높음 |
| 부수 효과 없어 예측 가능 | 불변성으로 메모리 사용 증가 |
| 병렬 처리에 유리 | 모든 상황에 적합하지 않음 |
| 코드 가독성 향상 | Java는 완전한 함수형 언어 아님 |
Java 8부터 Stream API, 람다, 함수형 인터페이스(Function, Predicate, Consumer 등) 가 도입되어 함수형 프로그래밍 스타일을 지원합니다.