1️⃣ 함수형 인터페이스란?
Java 8에서 도입된 Lambda 표현식과 함수형 프로그래밍 특성은 Java 함수형 인터페이스의 기초가 되었습니다. 이전 버전의 Java는 순수하게 객체지향 언어로 설계되었지만, 다른 언어에서 함수형 프로그래밍이 점점 더 인기를 얻으면서 Java도 이 패러다임을 수용하기 시작했습니다.
함수형 인터페이스란 단 하나의 추상 메서드만 가진 인터페이스를 의미합니다. 이러한 인터페이스는 Lambda 표현식이나 메서드 참조에 의해 대체될 수 있으며, 이를 통해 코드를 더욱 간결하고 유연하게 작성할 수 있습니다. Java 8에서는 java.util.function 패키지 내에 여러 가지 기본 제공 함수형 인터페이스를 추가했는데, 대표적으로는 Function, Predicate, 그리고 Consumer 등이 있습니다.
함수형 인터페이스는 Java에서 함수형 프로그래밍 스타일을 가능하게 하며, 이를 통해 메서드를 일급 시민(first-class citizen)으로 다룰 수 있게 됩니다. 이를 이용하면 반복문이나 필터링 같은 일반적인 패턴을 더 적은 코드로 표현할 수 있고, 병렬 처리와 고급 프로그래밍 기법을 위한 기반을 마련합니다.

2️⃣ 장단점
함수형 인터페이스는 다음과 같은 장점이 있습니다:
- 간결성: 함수형 인터페이스는 행동을 정의하는 데 필요한 코드량을 줄여줍니다.
- 가독성: Lambda 표현식을 사용하면 구현 세부 사항보다 수행할 작업을 강조하여 코드를 더 쉽게 이해할 수 있습니다.
- 병렬 처리 용이성: 상태나 부작용을 최소화하도록 설계되어 있어 병렬 처리가 더 쉬워집니다.
하지만 아래와 같은 제한 사항도 존재합니다:
- 학습 곡선: 함수형 프로그래밍 경험이 없는 개발자에게는 학습 비용이 필요할 수 있습니다.
- 가독성 저하: 복잡한 논리나 긴 코드에서는 Lambda 표현식이 오히려 가독성을 떨어뜨릴 수 있습니다.
3️⃣ 사용 방법
3.1 함수형 인터페이스 정의
함수형 인터페이스는 단 하나의 추상 메서드만 포함하는 인터페이스입니다. Lambda 표현식이나 메서드 참조의 타깃으로 사용될 수 있습니다.
아래는 함수형 인터페이스를 정의하는 예제입니다:
@FunctionalInterface
interface MyAction {
void perform();
default void log() {
System.out.println("기본 동작 실행");
}
static void notify() {
System.out.println("정적 메서드 호출");
}
}
위 코드에서 @FunctionalInterface 어노테이션은 해당 인터페이스가 함수형 인터페이스임을 나타냅니다. 이 어노테이션은 선택적이지만, 추천되며 다른 개발자들이 이를 쉽게 인식하도록 돕습니다.
함수형 인터페이스는 단 하나의 추상 메서드만 가질 수 있지만, 기본 메서드와 정적 메서드는 추가로 포함할 수 있습니다. 기본 메서드는 구현을 제공하며, 기존 코드를 깨지 않고 새로운 기능을 추가할 수 있도록 해줍니다. 정적 메서드는 특정 인터페이스 인스턴스와 관련 없이 유틸리티 메서드를 제공하는 데 유용합니다.
3.2 실전 예제
주문 시스템을 가정해보겠습니다. 주문 클래스 Order에는 주문 ID, 고객 이름, 주문 금액 등의 정보가 포함됩니다. 우리는 주문 목록에서 특정 조건에 맞는 주문들을 필터링해야 합니다. 이를 위해 함수형 인터페이스를 사용해 보겠습니다.
먼저, OrderCondition이라는 함수형 인터페이스를 정의하고, 이를 사용해 주문을 필터링하는 도구 클래스 OrderHandler를 만들어보겠습니다.
// 함수형 인터페이스 정의
@FunctionalInterface
interface OrderCondition {
boolean check(Order order);
}
// 주문 클래스
class Order {
private int id;
private String name;
private double price;
public Order(int id, String name, double price) {
this.id = id;
this.name = name;
this.price = price;
}
public int getId() { return id; }
public String getName() { return name; }
public double getPrice() { return price; }
}
// 도구 클래스
class OrderHandler {
public static List<Order> filter(List<Order> orders, OrderCondition condition) {
List<Order> result = new ArrayList<>();
for (Order order : orders) {
if (condition.check(order)) {
result.add(order);
}
}
return result;
}
}
이제 위의 함수형 인터페이스와 도구 클래스를 사용해 다양한 필터링 조건을 적용해봅시다.
import java.util.ArrayList;
import java.util.List;
public class MainApp {
public static void main(String[] args) {
List<Order> orders = new ArrayList<>();
orders.add(new Order(1, "김철수", 150));
orders.add(new Order(2, "이영희", 90));
orders.add(new Order(3, "박민수", 200));
// 익명 내부 클래스를 사용한 필터링
List<Order> filteredOrders = OrderHandler.filter(orders, new OrderCondition() {
@Override
public boolean check(Order order) {
return order.getPrice() > 100;
}
});
System.out.println("금액이 100 이상인 주문: " + filteredOrders);
// Lambda 표현식을 사용한 필터링
filteredOrders = OrderHandler.filter(orders, order -> order.getPrice() <= 100);
System.out.println("금액이 100 이하인 주문: " + filteredOrders);
}
}
위 코드는 Lambda 표현식을 통해 주문 필터링 조건을 더 간단히 정의할 수 있음을 보여줍니다.
3.3 팁 및 최적화
함수형 인터페이스를 사용할 때 다음 몇 가지 팁을 따르면 성능과 가독성을 향상시킬 수 있습니다:
- 부작용 방지: 함수 내부에서 변하는 상태를 피하여 무부작용 함수를 유지합니다.
- 병렬 처리 활용: 스트림 API 등을 통해 병렬 처리를 사용해 성능을 향상시킵니다.
- 메서드 참조 활용: 이미 존재하는 메서드를 단순히 호출하는 경우 메서드 참조를 사용해 코드를 더 깔끔하게 만듭니다.
4️⃣ 내장 함수형 인터페이스
Java 8에서는 함수형 프로그래밍을 지원하기 위해 다양한 내장 함수형 인터페이스를 제공합니다. 몇 가지 주요 인터페이스는 다음과 같습니다:
Runnable: 매개변수가 없고 반환값도 없는 코드 블록을 나타내며, 멀티스레드 환경에서 자주 사용됩니다.Supplier<t>: 입력값 없이 결과를 생성하는 역할을 합니다.Consumer<t>: 입력값을 소비하며 반환값은 없습니다.Function<t r="">: 입력값을 받아 결과를 반환합니다.Predicate<t>: 입력값을 받아 불리언 값을 반환하며, 조건 판단에 사용됩니다.
아래는 일부 내장 함수형 인터페이스를 사용하는 예제입니다:
import java.util.function.*;
public class BuiltInExample {
public static void main(String[] args) {
// Supplier<T>: 임의의 정수 생성
Supplier<Integer> randomSupplier = () -> (int)(Math.random() * 100);
// Consumer<T>: 값 출력
Consumer<Integer> printer = num -> System.out.println("값: " + num);
// Function<T, R>: 문자열 변환
Function<Integer, String> converter = num -> "숫자: " + num;
// Predicate<T>: 짝수 여부 확인
Predicate<Integer> isEven = num -> num % 2 == 0;
// 실행 예시
int randomNumber = randomSupplier.get();
printer.accept(randomNumber);
String converted = converter.apply(randomNumber);
System.out.println(converted);
boolean evenCheck = isEven.test(randomNumber);
System.out.println("짝수 여부: " + evenCheck);
}
}
5️⃣ 활용 사례
함수형 인터페이스는 다음과 같은 상황에서 주로 사용됩니다:
- 함수 전달: 다른 메서드에 함수를 전달하여 콜백 메커니즘, 이벤트 처리 등을 구현합니다.
- 콜백 메커니즘: 특정 이벤트 발생 시 함수형 인터페이스의 메서드를 호출하여 로직을 위임합니다.
- 이벤트 처리: 리스너로 바인딩하여 이벤트 발생 시 콜백 메서드를 호출합니다.
- 동적 분파: 다형성을 활용하여 다양한 함수형 인터페이스 구현을 전달받아 서로 다른 동작을 수행합니다.
- 함수 반환: 함수 자체를 반환하여 지연 실행, 게으른 계산 등을 구현합니다.