함수형 인터페이스란? 초보자를 위한 친절한 가이드

발행: (2025년 12월 10일 오후 12:06 GMT+9)
9 min read
원문: Dev.to

Source: Dev.to

소개

Java 코드를 작성하면서 “왜 이렇게 반복적인가?” 라고 생각해 본 적이 있나요? 혹은 최신 Java 코드에서 람다를 사용하고 있는 모습을 보고 그 뒤에서 어떻게 동작하는지 궁금했을 수도 있습니다. 바로 여기서 함수형 인터페이스가 등장합니다. 함수형 인터페이스는 람다, 스트림, 동시성 유틸리티 등 Java의 함수형 프로그래밍 기능을 뒷받침하는 핵심 요소입니다.

현대 Java 프로그래밍에서는 더 깔끔하고 표현력이 풍부한 코드를 작성하는 것이 필수이며, 함수형 인터페이스가 바로 그 목표를 달성하도록 도와줍니다. Java 8 이상—특히 Java 21—을 사용해 본 적이 있다면, 인식하든 못하든 이미 그 혜택을 누리고 있는 것입니다. 이 가이드는 함수형 인터페이스가 무엇인지, 왜 중요한지, 그리고 초보자가 어떻게 자신 있게 활용할 수 있는지를 단계별로 설명합니다.

핵심 개념

함수형 인터페이스란?

함수형 인터페이스는 추상 메서드가 정확히 하나만 선언된 Java 인터페이스입니다. 추상 메서드가 하나뿐이기 때문에 Java는 이를 람다 표현식으로 변환할 수 있습니다—즉, 행동을 데이터처럼 전달하는 짧고 표현력 있는 방법이 됩니다.

대표적인 내장 함수형 인터페이스는 다음과 같습니다:

  • Runnable — 입력도 없고 반환값도 없는 작업을 나타냅니다
  • Callable — 값을 반환하는 작업
  • Predicate — 값을 받아서 boolean을 반환
  • Function — 한 타입의 값을 다른 타입으로 변환
  • Consumer — 값을 처리하지만 반환값은 없음

함수형 인터페이스가 중요한 이유

Java 8 이전에는 행동을 전달하려면 익명 클래스를 사용해야 했으며, 간단한 로직이라도 6~8줄의 보일러플레이트 코드가 필요했습니다. 함수형 인터페이스는 이 문제를 해결해 주어 Java가 함수형 프로그래밍 기능을 지원하도록 하며, 코드를 다음과 같이 개선합니다:

  • 짧아짐 – 줄 수가 줄고 보일러플레이트가 감소
  • 명확해짐 – 로직을 이해하기 쉬워짐
  • 유연해짐 – 메서드를 변수처럼 전달 가능
  • 재사용 가능 – 하나의 인터페이스로 다양한 람다 표현식을 지원

간단한 비유

하나의 작업, 예를 들어 식물에 물 주기만을 담당할 사람을 고용한다고 생각해 보세요. 어떻게 물을 주는지는 중요하지 않고, 그 단일 행동을 수행할 수 있는지만 중요합니다. 이것이 바로 함수형 인터페이스—단일 책임을 가진 역할입니다.

함수형 인터페이스를 어디에 쓰나요?

거의 모든 곳에서 마주칩니다:

  • Streams API (map, filter, forEach)
  • 동시성 (ExecutorService, CompletableFuture)
  • 이벤트‑드리븐 시스템
  • 컬렉션 변환
  • 람다를 활용한 사용자 정의 비즈니스 로직

함수형 인터페이스는 Java를 Kotlin, Python 같은 현대 언어와 비슷하게 동작하게 하면서도 강력한 타입과 구조를 유지하도록 도와줍니다.

코드 예제 (Java 21)

예제 1: 사용자 정의 함수형 인터페이스 만들기 및 사용하기

@FunctionalInterface
interface GreetingService {
    void greet(String name); // Single abstract method
}

public class FunctionalInterfaceDemo {
    public static void main(String[] args) {
        // Using a lambda expression to implement the interface
        GreetingService greeter = (name) ->
                System.out.println("Hello, " + name + "! Welcome to Java 21.");

        // Calling the method
        greeter.greet("Nil");
    }
}
  • @FunctionalInterface 로 함수형 인터페이스를 정의하는 방법을 보여줍니다.
  • 람다 표현식으로 구현하는 과정을 시연합니다.
  • 깔끔하고 가독성 높은 행동 전달 방식을 제공합니다.

예제 2: 내장 함수형 인터페이스(Function & Predicate) 사용하기

import java.util.function.Function;
import java.util.function.Predicate;

public class BuiltInFunctionDemo {
    public static void main(String[] args) {
        // Predicate: checks if a number is even
        Predicate<Integer> isEven = num -> num % 2 == 0;

        // Function: squares a number
        Function<Integer, Integer> square = n -> n * n;

        int number = 6;

        if (isEven.test(number)) {
            System.out.println(number + " is even.");
            System.out.println("Its square is: " + square.apply(number));
        } else {
            System.out.println(number + " is odd.");
        }
    }
}
  • 내장 함수형 인터페이스를 실제로 활용하는 예시입니다.
  • 람다 표현식으로 재사용 가능한 로직을 깔끔하게 표현합니다.
  • 함수가 데이터를 어떻게 변환하고 평가하는지 보여줍니다.

모범 사례

  1. @FunctionalInterface 어노테이션 사용 – 선택 사항이지만 컴파일 시 실수를 잡아주는 데 도움이 됩니다.
  2. 람다 표현식은 짧고 읽기 쉽게 – 람다가 너무 길어지면 이름 있는 메서드로 추출하세요.
  3. 내장 함수형 인터페이스를 우선 사용 – Java는 Function, Supplier, Predicate, Consumer 등 다양한 인터페이스를 제공합니다. 특별한 경우가 아니라면 직접 정의하지 마세요.
  4. 함수형 인터페이스 남용을 피함 – 모든 문제가 람다로 해결되는 것은 아닙니다. 전통적인 클래스나 메서드가 더 명확할 때도 있습니다.
  5. 추상 메서드는 정확히 하나만 – default 메서드와 static 메서드는 제외되지만, 실수로 추가 추상 메서드를 넣지 않도록 주의하세요.

결론

함수형 인터페이스는 현대 Java에서 도입된 가장 강력한 기능 중 하나입니다. 코드를 더 깔끔하고 이해하기 쉬우며 유연하게 만들어 줍니다. 스트림, 비동기 작업, 간단한 데이터 변환 등 어느 상황에서든 함수형 인터페이스는 우아하고 표현력 있는 코드를 작성하기 위한 기반을 제공합니다. Java 여정을 일찍 시작할수록, 함수형 인터페이스를 마스터함으로써 전문적인 수준의 애플리케이션을 손쉽게 구현할 수 있습니다. 직접 함수형 인터페이스를 만들어 보거나 Java의 내장 인터페이스를 실험해 보세요—연습할수록 자연스럽게 익숙해질 것입니다.

Back to Blog

관련 글

더 보기 »