티스토리 뷰
함수형 프로그래밍은 소프트웨어 개발에서 점차 중요한 패러다임으로 자리 잡고 있습니다. 이는 상태 변경을 최소화하고 함수를 일급 객체 취급하여 코드의 가독성과 유지보수성을 높이는 접근법입니다. Java는 8 버전부터 람다 표현식과 함수형 인터페이스를 도입함으로써 이러한 패러다임을 지원하기 시작했습니다. 이번 포스팅에서는 Java 의 함수형 인터페이스 중 Supplier 와 Consumer 에 초점을 맞춰 설명하겠습니다. 이 인터페이스들은 데이터 제공과 소비를 추상화하여 코드의 모듈성과 재사용성을 강화합니다. 간단한 예시를 통해 알아보겠습니다.
1. Supplier 인터페이스
supplier 인터페이스는 Java.util.function 패키지에 속하며, 매개변수를 받지 않고 결과를 반환하는 함수를 나타냅니다.
@FunctionalInterface
public interface Supplier<T> {
T get();
}
이 인터페이스는 지연 평가(Lazy evaluation)를 구현할 때 유용합니다. 예를 들어, 값이 필요할 때만 계산하거나 기존 객체의 속성을 동적으로 추출하는 데 활용됩니다. Supplier는 불변성을 유지하면서도 동적인 데이터 접근을 가능하게 하여, 함수형 프로그래밍의 핵심 원칙을 지킵니다. 실제로 Supplier 는 캐싱이나 초기화 지연과 같은 패턴에서 자주 사용되며, 코드의 성능을 최적화하는데 기여합니다.
2. Consumer 인터페이스
Consumer 인터페이스는 마찬가지로 Java.util.function 패키지에 속하며, 입력을 받아들이고 결과를 반환하지 않는 함수를 정의합니다.
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
이 인터페이스는 부작용(side effect)을 발생시키는 연산, 예를 들어 객체의 상태 변경이나 로깅을 담당합니다. 함수형 프로그래밍에서 Consumer 는 순수 함수와 결합되어 전체적인 로직을 구성할 때 필수적입니다. 이를 통해 코드의 책임을 분리하고, 테스트 용이성을 높일 수 있습니다. Consumer는 forEach 메서드나 이벤트 핸들링과 같은 시나리오에서 광범위하게 적용됩니다.
3. 예시
해당 예시는 "값 변경 감지 유틸리티" 를 구현한 것으로, 객체의 속성 업데이트 시 변경 여부를 감지하는 유틸리티를 구성해 보겠습니다.
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Supplier;
public final class ValueUpdateHelper {
private ValueUpdateHelper() {
// 인스턴스 생성 방지
}
/**
* 새로운 값이 기존 값과 다를 경우에만 setter를 호출합니다.
* null 또는 빈 값은 무시하고 기존 값을 유지합니다.
*
* @param <T> 값의 타입
* @param newValue 새로운 값
* @param oldValueSupplier 기존 값을 공급하는 Supplier
* @param setter 새로운 값을 설정하는 Consumer
*/
public static <T> void updateIfDifferent(T newValue, Supplier<T> oldValueSupplier, Consumer<T> setter) {
if (newValue == null) {
return; // null 무시
}
Object normalizedValue = newValue;
if (newValue instanceof String str && str.trim().isEmpty()) {
normalizedValue = null; // 공백 문자열 정규화
} else if (newValue instanceof List<?> lst && lst.isEmpty()) {
normalizedValue = null; // 빈 리스트 정규화
} // 추가 타입에 대한 정규화는 필요 시 확장 가능
T oldValue = oldValueSupplier.get();
if (!Objects.equals(normalizedValue, oldValue)) {
setter.accept((T) normalizedValue);
}
}
}
- 새로운 값이 null 이거나 빈 문자열/컬렉션인 경우, 기존 값을 유지합니다. 이는 데이터 무결성을 보호하며 불필요한 업데이트를 방지합니다.
- null-safe 한 비교를 위해 표준 라이브러리인 Objects.equals 를 사용합니다.
- 다양한 타입에 적용이 가능하도록 제네릭 타입으로 설계하였습니다.
사용예시
import static ~.ValueUpdateHelper.updateIfDifferent;
...
// ClientInfo 클래스 가정 (getter/setter 포함)
ClientInfo client = new ClientInfo();
ClientUpdateDto request = new ClientUpdateDto();
// 업데이트 로직
updateIfDifferent(request.getCategoryCode(), client::getCategoryCode, client::setCategoryCode); // 카테고리 코드
updateIfDifferent(request.getSectorId(), client::getSectorId, client::setSectorId); // 섹터 ID
updateIfDifferent(request.getDetailSectorId(), client::getDetailSectorId, client::setDetailSectorId); // 상세 섹터 ID
- 이 접근법은 명령형 코드에 비해 더 선언적이며, Supplier 와 Consumer를 통해 속성 접근을 추상화합니다. 결과적으로 코드가 간결해지고, 오류 가능성이 줄어듭니다.
4. 장점과 고려사항
장점
- 모듈성: 로직을 재사용 가능한 유틸리티로 캡슐화
- 성능 최적화: 변경되지 않은 값에 대한 불필요한 setter 호출 방지
- 함수형 원칙 준수: 상태 변경을 최소화하고, 함수를 통해 연산 분리
고려사항
- 복잡한 타입(예: 커스텀 객체)에 대한 equals 메서드 구현이 필요하며, 다중 스레드 환경에서는 동시성 문제를 고려해야 합니다.
5. 결론
함수형 프로그래밍은 Java 개발자에게 강력한 도구를 제공하며, Supplier 와 Consumer 는 핵심 요소 중 하나입니다. 이번 포스팅에서 제시한 예시 이외에도 활용할 가치가 있으니 이를 바탕으로 코드베이스에 적용해 보시기 바랍니다.
감사합니다.
'프레임워크 > SpringBoot' 카테고리의 다른 글
[SpringBoot] JPA 낙관락과 비관락으로 동시성 제어하기 (0) | 2025.09.05 |
---|---|
[SpringBoot] Redis와 EHCache 함께 사용하는 방법 (0) | 2025.09.03 |
[JPA] 더티 체킹이란? (2) | 2025.08.26 |
[SpringBoot] MyBatis 다중 Datasource 적용하기(@Qualifier) - Mapper 경로 공유 (0) | 2025.04.09 |
[SpringBoot] HTTP 요청 다루기(@RequestBody, @RequestParam, @ModelAttribute) (0) | 2025.04.08 |
- Total
- Today
- Yesterday