자바는 전통적으로 객체지향 프로그래밍(OOP) 언어로 알려져 있습니다. 하지만 Java 8부터 함수형 프로그래밍(Functional Programming, FP) 을 지원하면서 코드의 간결성과 유지보수성을 높일 수 있는 새로운 패러다임이 도입되었습니다.
이번 글에서는 기초 개념부터 고급 활용까지 자세히 살펴보며, 실제 예제와 함께 자바에서 함수형 프로그래밍을 어떻게 적용할 수 있는지 알아보겠습니다.
1. 함수형 프로그래밍이란?
함수형 프로그래밍은 부수 효과(side-effect)를 최소화하고, 상태를 변경하지 않으며, 함수를 일급 객체로 다루는 프로그래밍 스타일입니다.
- 일급 객체(First-class Citizen): 함수도 변수처럼 다룰 수 있습니다.
- 순수 함수(Pure Function): 동일한 입력에 대해 항상 동일한 출력, 외부 상태 변경 없음.
- 고차 함수(Higher-order Function): 함수를 인자로 받거나, 함수를 반환하는 함수.
왜 함수형 프로그래밍이 유용한가?
- 가독성 향상: 선언적 코드 작성으로 로직이 명확합니다.
- 병렬 처리 용이: 상태 변화를 최소화해 멀티스레드 환경에서도 안전합니다.
- 재사용성 증가: 작은 함수 단위로 조합이 쉽습니다.
2. 자바에서 함수형 프로그래밍 지원
Java 8부터는 다음과 같은 기능으로 함수형 프로그래밍을 지원합니다:
- 람다(Lambda Expression)
- 함수형 인터페이스(Functional Interface)
- Stream API
- Optional
2.1 람다 표현식
람다는 **익명 함수(anonymous function)**를 만들 수 있는 표현식입니다.
// 기존 방식
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello, World!");
}
};
// 람다 표현식
Runnable r2 = () -> System.out.println("Hello, World!");
람다는 함수형 인터페이스와 함께 사용됩니다.
2.2 함수형 인터페이스
함수형 인터페이스는 단 하나의 추상 메서드만 가진 인터페이스입니다.
대표적인 자바 기본 함수형 인터페이스:
인터페이스 설명
Function<T, R> | 입력 T, 출력 R |
Consumer<T> | 입력 T, 반환 없음 |
Supplier<T> | 입력 없음, 출력 T |
Predicate<T> | 입력 T, Boolean 반환 |
Function<String, Integer> lengthFunc = str -> str.length();
System.out.println(lengthFunc.apply("Hello")); // 5
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // true
2.3 Stream API
Stream은 데이터 처리의 파이프라인을 제공하며, 함수형 스타일로 컬렉션을 처리할 수 있습니다.
List<String> names = List.of("Alice", "Bob", "Charlie");
// 1. filter -> 2. map -> 3. collect
List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("A"))
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(filteredNames); // [ALICE]
- filter: 조건에 맞는 요소만 걸러냄
- map: 요소 변환
- collect: 결과 수집
2.4 Optional
Optional은 null 안전 처리를 위해 사용되며, 함수형 스타일로 접근할 수 있습니다.
Optional<String> optional = Optional.of("Hello");
optional.map(String::toUpperCase)
.filter(s -> s.startsWith("H"))
.ifPresent(System.out::println); // HELLO
Optional은 null 체크를 함수형으로 대체합니다.
3. 함수형 프로그래밍 심화
3.1 고차 함수 활용
함수를 인자로 받거나 반환하는 패턴은 재사용성을 극대화합니다.
// 함수 반환
Function<Integer, Integer> add(int x) {
return y -> x + y;
}
Function<Integer, Integer> addFive = add(5);
System.out.println(addFive.apply(10)); // 15
3.2 메서드 참조(Method Reference)
람다 표현식을 더 간결하게 표현할 수 있습니다.
List<String> names = List.of("Alice", "Bob");
names.forEach(System.out::println);
- Class::staticMethod
- instance::instanceMethod
- Class::new (생성자 참조)
3.3 함수형 디자인 패턴
- 커링(Currying): 다중 인자를 갖는 함수를 단일 인자를 갖는 함수로 변환
- 합성 함수(Function Composition): 여러 함수를 조합
Function<Integer, Integer> multiply2 = x -> x * 2;
Function<Integer, Integer> add3 = x -> x + 3;
Function<Integer, Integer> combined = multiply2.andThen(add3);
System.out.println(combined.apply(5)); // 13
4. 함수형 프로그래밍과 OOP 결합
자바에서는 함수형과 객체지향을 함께 사용할 수 있습니다.
예를 들어 서비스 레이어에서 Stream과 Optional을 활용하여 데이터를 안전하고 간결하게 처리할 수 있습니다.
public class UserService {
public Optional<String> getUserEmail(Long userId) {
return Optional.ofNullable(findUserById(userId))
.map(User::getEmail);
}
}
- 장점: null 체크 최소화, 명시적 데이터 흐름, 재사용 가능
5. 함수형 프로그래밍 실무 팁
- 작은 함수 단위로 쪼개기: 재사용성과 테스트 용이성 증가
- 불변 객체 활용: 상태 변경 최소화
- Stream 연산 최적화: parallelStream() 신중 사용
- Optional 남용 금지: 반환 값이 반드시 존재하는 경우는 일반 객체 사용
'Java' 카테고리의 다른 글
[Java] 가비지 컬렉션(GC) 종류별 비교 및 선택 방법 (0) | 2025.04.11 |
---|---|
[Java] Record, Sealed Class를 활용한 객체 생성 및 관리 (0) | 2025.04.02 |
[Java] Java 원격 디버깅(Java Remote Debugging) (0) | 2025.01.08 |
[Java] JVM의 한계와 GraalVM 살펴보기 (1) | 2024.12.16 |
[Java] JVM Warm up - if(kakao)2022 (1) | 2024.11.11 |