-
함수형 인터페이스를 알아보자백엔드 : 서버공부/Spring 2026. 4. 19. 17:50728x90
최근 버전의 자바로 개발하다 보면 아래와 같은 코드를 보게 된다.
load(semaphore, () -> userServicePort.getName());처음 이 문법을 보면 조금 생소하다.
load메서드에semaphore와 함께 람다식을 넘기고 있기 때문이다.
기존 자바 문법에 익숙한 사람이라면 보통 값이나 객체를 파라미터로 넘기는 데는 익숙하지만, 이렇게 어떤 동작 자체를 전달하는 형태는 직관적으로 와닿지 않을 수 있다.하지만 이 코드는 단순히 문법이 새로워진 것이 아니라, 자바가 동작을 더 유연하게 다룰 수 있도록 만든 방식의 결과다.
그 중심에는 글의 주제인 함수형 인터페이스가 있다.
함수형 인터페이스는 추상 메서드가 하나만 있는 인터페이스를 말한다.
자바는 메서드 자체를 다른 메서드의 파라미터로 직접 전달할 수는 없지만,어떤 동작을 담고 있는 객체를 전달하는 방식으로 이 문제를 해결했다.
함수형 인터페이스는 바로 그 동작을 담는 그릇 역할을 한다.
대표적인 함수형 인터페이스로는
Runnable,Callable,Comparator등이 있다.이번 글에서는
Callable을 예시로 보겠다.Callable은 아래와 같이 정의되어 있다.public interface Callable<V> { V call() throws Exception; }여기서 중요한 점은
call()메서드에 구현이 들어 있는 것이 아니라,이런 형태의 메서드를 구현해야 한다는 계약만 정의되어 있다는 것이다.
즉Callable자체는 실행할 로직을 가지고 있지 않다.
실제 구현은 이 인터페이스를 사용하는 쪽에서 작성한다.예를 들어 예전 방식으로 작성하면 아래와 같이 구현할 수 있다.
Callable<String> callable = new Callable<>() { @Override public String call() throws Exception { return userServicePort.getName(); } };이 코드는 문제가 없지만 하는 역할에 비해 장황하다.
Callable은 추상 메서드가 하나뿐인 함수형 인터페이스이기 때문에, 이 구현을 람다식으로 더 간결하게 표현할 수 있다.Callable<String> callable = () -> userServicePort.getName();람다식이 처음 보면 특별한 문법처럼 느껴질 수 있지만,
결국 위 코드는 아래 익명 클래스 구현과 같은 의미를 가진다.new Callable<String>() { @Override public String call() throws Exception { return userServicePort.getName(); } }즉
() -> userServicePort.getName()은Callable의call()메서드를 구현한 코드라고 이해하면 된다.
메서드가 하나뿐이기 때문에 자바 컴파일러도 이 람다가 어떤 메서드의 구현인지 헷갈리지 않아 오류도 발생하지않는다.이제 처음의 예제를 다시 보면 조금 더 자연스럽게 읽힐 것이다.
load(semaphore, () -> userServicePort.getName());이 코드는
userServicePort.getName()이라는 동작을Callable형태로 감싸서load메서드에 전달하는 코드다.즉
load는 값을 직접 받는 것이 아니라, 나중에 실행할 작업을 파라미터로 전달받는다.이때
load메서드는 보통 아래와 같이 구현되어 있을 것이다.private <T> T load(Semaphore semaphore, Callable<T> callable) { try { semaphore.acquire(); return callable.call(); } catch (Exception e) { throw new RuntimeException(e); } finally { semaphore.release(); } }여기서 핵심은
callable.call()이다.load메서드는 먼저 세마포어를 획득하고, 그 다음 파라미터로 전달받은 작업을 call을 통해 실행한다.다시 말해 이 메서드는 세마포어를 획득하고 해제하는 공통 로직과, 실제 비즈니스 로직을 분리하고 있는 것이다.
이 방식의 장점은 중복제거와 재사용에 있다.
만약 세마포어를 사용해야 하는 코드가 여러 곳에 흩어져 있다면,
매번
try-catch-finally를 작성하며 락 획득과 해제를 반복해야 한다.
그런데 함수형 인터페이스를 사용하면 공통 제어 로직은load메서드 안에 모아두고, 실제 실행할 작업만 바꿔서 전달할 수 있다.예를 들어 아래와 같은 코드가 가능해진다.
String name = load(semaphore, () -> userServicePort.getName()); Integer age = load(semaphore, () -> userServicePort.getAge()); Boolean active = load(semaphore, () -> userServicePort.isActive());세 코드 모두 세마포어를 획득하고 해제하는 방식은 동일하지만, 실제로 수행하는 작업은 다르다.
공통 로직은 한곳에 모이고, 변경되는 부분만 외부에서 전달된다.
이런 구조는 중복 코드를 줄이고, 코드의 의도를 더 분명하게 만들어준다. (재사용성이 높은 메서드 구현가능)결국 함수형 인터페이스의 핵심은 동작을 값처럼 다룰 수 있게 해준다는 데 있다.
마무리
자바는 메서드 자체를 직접 넘기지는 못하지만, 함수형 인터페이스와 람다식을 통해 동작을 전달할 수 있게 되었다.
덕분에 공통 로직을 추상화하고, 실행할 작업만 외부에서 주입하는 방식의 코드가 훨씬 자연스럽게 가능해졌다.처음에는
load(semaphore, () -> userServicePort.getName())같은 코드가 생소하게 느껴질 수 있다.
하지만 이것을 “메서드를 넘긴다”기보다는 “실행할 작업을 넘긴다”라고 이해하면 훨씬 쉽게 받아들일 수 있다.그리고 함수형 인터페이스는 바로 그 작업을 담아두는 표준화된 그릇이다.
찐 마무리
요즘 날씨가 너무 좋다.
다들 개발도 좋지만, 1년에 한번 뿐인 봄도 즐기고 그러시길!

'백엔드 : 서버공부 > Spring' 카테고리의 다른 글
Lambda invoke를 기다리지 않기: 전자위임장 출력 비동기화 개선기 (0) 2026.05.10 람다로 무거운 작업 분리하기 (0) 2026.05.02 고대 자바 개발자들은 완전히 멘탈이 나가버렸습니다 : NPE (0) 2026.02.18 두바이쫀득쿠키로 알아보는 추상화 (0) 2026.01.09 Spring Boot 4.0의 등장 : 작아지고 빨라지고 (0) 2025.12.10