📍람다를 쓰기 위해 인터페이스를 만들자?
자바에서 람다를 사용하기 위해선 반드시 함수형 인터페이스가 필요하다.
가장 기본적인 사용 형태를 아래 예제에서 볼 수 있다.
public class GenericMain1 {
public static void main(String[] args) {
StringFunction upperCase = s -> s.toUpperCase();
String result1 = upperCase.apply("hello");
System.out.println("result1 = " + result1);
NumberFunction square = n -> n * n;
Integer result2 = square.apply(3);
System.out.println("result2 = " + result2);
}
@FunctionalInterface
interface StringFunction {
String apply(String s);
}
@FunctionalInterface
interface NumberFunction {
Integer apply(Integer s);
}
}
람다식은 아주 간결하고 유용하지만, 매개변수와 반환값의 타입이 달라질 때마다 새로운 인터페이스를 계속 만들어야 하는 번거로움이 있다. 어떻게 해결할 수 있을까?
- 방법 1: Object 타입으로 통일?
interface ObjectFunction {
Object apply(Object s);
}
→ 가능은 하지만, 타입 캐스팅이 필수이기 때문에 타입 안정성이 떨어진다.
매번 instanceof, (String) 등의 형변환이 필요하게 됨.
- 방법 2: 제네릭으로 일반화
@FunctionalInterface
interface GenericFunction<T, R> {
R apply(T s);
}
이렇게 하면 입력 타입 T, 반환 타입 R을 필요할 때마다 지정할 수 있으니 하나의 인터페이스로 모든 상황을 처리할 수 있다.
하지만 위 방법들은 모두 모든 개발자가 똑같은 코드를 작성해야한다는 문제가 있다.
📍자바가 제공하는 표준 함수형 인터페이스를 활용하자
자바는 이런 문제들을 해결하기 위해 필요한 함수형 인터페이스 대부분을 기본으로 제공한다.
자바가 제공하는 함수형 인터페이스를 사용하면, 비슷한 함수형 인터페이스를 불필요하게 만드는 문제는 물론이고, 함 수형 인터페이스의 호환성 문제까지 해결할 수 있다.
자바가 만들어 놓아서 모든 개발자가 같은 함수형 인터페이스를 사용하니, 서로 대입도 가능해진다. 안 쓸 이유가 없다.
대표적인 표준 함수형 인터페이스는 아래와 같다.
Function 인터페이스
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
하나의 매개변수를 받고, 결과를 반환하는 함수형 인터페이스이다.
입력값(T )을 받아서 다른 타입의 출력값(R )을 반환하는 연산을 표현할 때 사용한다. 물론 같은 타입의 출력 값도 가능하다.
일반적인 함수(Function)의 개념에 가장 가깝다. ex) 문자열을 받아서 정수로 변환, 객체를 받아서 특정 필드 추출 등
Consumer 인터페이스
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
입력 값(T)만 받고, 결과를 반환하지 않는( void ) 연산을 수행하는 함수형 인터페이스이다.
입력값(T)을 받아서 처리하지만 결과를 반환하지 않는 연산을 표현할 때 사용한다.
입력 받은 데이터를 기반으로 내부적으로 처리만 하는 경우에 유용하다.
예) 컬렉션에 값 추가, 콘솔 출력, 로그 작성, DB 저장 등
Supplier 인터페이스
@FunctionalInterface
public interface Supplier<T> {
T get();
}
입력을 받지 않고( () ) 어떤 데이터를 공급(supply)해주는 함수형 인터페이스이다.
객체나 값 생성, 지연 초기화 등에 주로 사용된다.
Runnable 인터페이스
@FunctionalInterface
public interface Runnable {
void run();
}
입력값도 없고 반환값도 없는 함수형 인터페이스이다.
자바에서는 원래부터 스레드 실행을 위한 인터페이스로 쓰였지만, 자바 8 이후에는 람다식으로도 많이 표현된다.
자바8로 업데이트 되면서 @FunctionalInterface 어노테이션도 붙었다.
java.lang 패키지에 있다. 자바의 경우 원래부터 있던 인터페이스는 하위 호환을 위해 그대로 유지한다.
주로 멀티스레딩에서 스레드의 작업을 정의할 때 사용한다. 입력값도 없고, 반환값도 없는 함수형 인터페이스가 필요할 때 사용한다.
📍특화 함수형 인터페이스
특화 함수형 인터페이스는 의도를 명확하게 만든 조금 특별한 함수형 인터페이스다.
Predicate : 입력O, 반환 boolean 조건 검사, 필터링 용도
Operator ( UnaryOperator , BinaryOperator ): 입력O, 반환O 동일한 타입의 연산 수행, 입력과 같은 타입을 반환하는 연산 용도
Predicate 인터페이스
FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
입력 값(T)을 받아서 true 또는 false 로 구분(판단)하는 함수형 인터페이스이다. 조건 검사, 필터링 등의 용도로 많이 사용된다.
넘어오는 값의 참/거짓만 판단한다고 생각하면 됨.
- 이것이 꼭 필요할까? function 인터페이스로 그냥 구현하면 되는 거 아닌가..
- 판단한다는 의도를 명시적으로 들어내기 위해서 만든 인터페이스이다.
- 물론 단순하게 만들 수 있지만, 별도로 둠으로써 의미가 명확해진다. + 가독성 (클린코드)
UnaryOperator 인터페이스
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
T apply(T t); // 실제 코드가 있지는 않음
}
오퍼레이터는 말 그대로 연산한다는 의미로 가져온 것이다.
입력(피연산자)과 결과(연산 결과)가 동일한 타입인 연산을 수행할 때 사용한다.
BinaryOperator 인터페이스
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
T apply(T t1, T t2); // 실제 코드가 있지는 않음
}
이항 연산은 두 개의 피연산자(operand)에 대해 연산을 수행하는 것을 말한다.
예:두수의덧셈(`x + y` ),곱셈(`x * y` )등
위 인터페이스 로직도 Function 인터페이스로 구현할 수 있는데, 의도와 가독성을 위해서 따로 정리해 둔 것이다.
📍기타 함수형 인터페이스
매개변수가 2개 이상 필요한 경우에는 BiXxx 시리즈를 사용하면 된다. Bi는 Binary(이항, 둘)의 줄임말이다.
ex) BiFunction , BiConsumer , BiPredicate
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
System.out.println("Sum: " + add.apply(5, 10));
BiConsumer<String, Integer> repeat = (c, n) -> {
for (int i = 0; i < n; i++) {
System.out.print(c);
}
System.out.println();
};
repeat.accept("*", 5);
BiPredicate<Integer, Integer> isGreater = (a, b) -> a > b;
System.out.println("isGreater: " + isGreater.test(10, 5));
}
다음과 같이 기본형(primitive type)을 지원하는 함수형 인터페이스도 있다.
@FunctionalInterface
public interface IntFunction<R> {
R apply(int value);
}
오토박싱, 언박싱 코스트도 줄이고 싶어서 이렇게 구현했는데 별 의미는 없다고 한다.
즉. 제네릭은 기본형 (int, double)을 사용할 수 없어서 이런 식으로 사용할 수 있도록 만들어 놓았다.
기본형이 있는지 궁금하면 앞에 기본형을 붙여서 인터페이스를 선언해보자. 있을 수도 있음.
참고 : 김영한의 실전 자바 - 고급 3편
'java' 카테고리의 다른 글
[Java] 람다가 필요한 이유와 생략 규칙 (0) | 2025.04.07 |
---|---|
[Java] 튜닝의 마지막 단계 GC 알아보기 (0) | 2025.02.05 |
[Java] Java 용어들을 알아보자. (JRE, JDK, JVM) (0) | 2025.02.01 |