1. 프로그래밍 패러다임 개요
- 명령형(Imperative): 어떻게(How) 실행할지를 상세히 기술
- 절차지향(Procedural), 객체지향(OOP)이 포함
- 선언형(Declarative): 무엇(What)을 할지를 선언하고 구현 방식은 추상화
- SQL, HTML, 함수형 프로그래밍 등이 속함
2. 함수형 프로그래밍이란?
- 정의: 순수함수(pure function)를 조합하고,부수효과(side effect)와 가변상태(mutable state)를 최소화하는 선언형 스타일
- 핵심 특징
- 순수 함수(Pure Function)
- 부수 효과 최소화 (Side Effect Minimization)
- 불변성(Immutable State)
- 일급 시민 함수(First-class Functions)
- 선언적 접근(Declarative)
- 함수 합성(Composition)
- 지연 평가(Lazy Evaluation) – 선택적 특성
3. 자바에서의 함수형 프로그래밍 주요 개념 및 예제
3.1 순수 함수 (Pure Function)
동일한 입력에 대해 항상 동일한 출력을 반환하고, 외부 상태를 변경하지 않음.
Function<Integer,Integer> pureFunc = x -> x * 2;
System.out.println(pureFunc.apply(10)); // 20
System.out.println(pureFunc.apply(10)); // 20
- 입력 10 → 출력 20이 항상 일정
3.2 부수 효과 최소화 (Side Effect Minimization)
함수 호출 시 외부 상태를 변경하거나 출력하지 않도록 분리
// ❌ 부수 효과 예시: 전역 변수 변경
public static int count = 0;
Function<Integer,Integer> badFunc = x -> { count++; return x * 2; };
badFunc.apply(10);
System.out.println(count); // 1
// ✅ 부수 효과 분리
Function<Integer,Integer> goodFunc = x -> x * 2;
int result = goodFunc.apply(10);
System.out.println("result = " + result); // 20
- 연산과 상태 변경(출력 등)을 분리하여 순수 함수를 유지
3.3 불변성 (Immutable State)
객체 상태를 변경하지 않고, 변경이 필요하면 새로운 객체를 생성
// MutablePerson: setter로 상태 변경 가능
MutablePerson m = new MutablePerson("Kim",10);
m.setAge(11); // 원본 변경
// ImmutablePerson: 필드가 final, 변경 시 새 객체 반환
ImmutablePerson i1 = new ImmutablePerson("Lee",20);
ImmutablePerson i2 = i1.withAge(21);
System.out.println(i1.getAge()); // 20
System.out.println(i2.getAge()); // 21
- withAge() 호출 시 새 객체를 반환하며 원본 불변
3.4 일급 시민 함수 (First-class Functions)
함수를 변수에 할당, 인자로 전달, 반환 가능
Function<Integer,Integer> func = x -> x * 2;
// 함수 인자로 전달
public static Integer applyFunc(int v, Function<Integer,Integer> f) {
return f.apply(v);
}
System.out.println(applyFunc(5, func)); // 10
// 함수 반환
public static Function<Integer,Integer> getFunc() {
return x -> x + 3;
}
Function<Integer,Integer> f2 = getFunc();
System.out.println(f2.apply(7)); // 10
- 고차 함수(Higher-order Function) 구현 가능
3.5 선언적 접근 (Declarative)
스트림 API로 “무엇을” 할지 명시
List<Integer> nums = List.of(1,2,3,4,5,6,7,8,9,10);
// 명령형
List<Integer> imp = new ArrayList<>();
for (int n : nums) {
if (n % 2 == 0) imp.add(n * n);
}
System.out.println(imp); // [4,16,36,64,100]
// 선언형
List<Integer> decl = nums.stream()
.filter(n -> n % 2 == 0)
.map(n -> n * n)
.toList();
System.out.println(decl); // [4,16,36,64,100]
- filter·map 체이닝으로 로직 간결
3.6 함수 합성 (Composition)
작은 함수를 연결해 복합 함수 생성
Function<Integer,Integer> square = x -> x * x;
Function<Integer,Integer> increment = x -> x + 1;
// compose: increment → square
Function<Integer,Integer> comp = square.compose(increment);
System.out.println(comp.apply(2)); // square(increment(2)) = 9
// andThen: square → increment
Function<Integer,Integer> seq = square.andThen(increment);
System.out.println(seq.apply(2)); // increment(square(2)) = 5
3.7 지연 평가 (Lazy Evaluation)
스트림 중간 연산은 최종 연산 시점에 실행
Stream<Integer> st = List.of(1,2,3,4,5).stream()
.filter(n -> { System.out.println("filter " + n); return n % 2 == 0; });
// 아직 출력 없음
List<Integer> evens = st.toList();
// 여기서야 filter 로그 출력
4. 정리
- 멀티 패러다임: 자바는 객체지향 언어이나, 람다·스트림으로 함수형 스타일 활용 가능
- 테스트·병렬성: 순수 함수,불변성 덕분에 디버깅 쉽고, 동시성 처리 안정적
- 유연한 조합: 프로젝트 상황에 맞춰 절차지향·OOP·함수형을 병행 사용
'Java' 카테고리의 다른 글
[Java] 김영한의 실전 자바 - 고급 3편 섹션12 병렬 스트림 (0) | 2025.04.28 |
---|---|
[Java] 김영한의 실전 자바 - 고급 3편 섹션11 Optional (1) | 2025.04.17 |
[Java] 김영한의 실전 자바 - 고급 3편 섹션7 스트림API (1) | 2025.04.12 |
[Java] 김영한의 실전 자바 - 고급 3편 섹션6 메서드 참조 (0) | 2025.04.12 |
[Java] 김영한의 실전 자바 - 고급 3편 섹션4,5 람다활용,익명클래스와의 차이 (+정적 팩토리 메서드) (0) | 2025.04.07 |