[Java] 함수형 인터페이스
함수형 인터페이스?
함수형 인터페이스는 오직 하나의 추상 메서드를 가지고 있는 인터페이스이다.
함수 디스크립터
함수형 인터페이스의 추상 메서드는 람다 표현식의 시그니처를 묘사한다.
함수형 인터페이스의 추상 메서드 시그니처를 함수 디스크립터라고 한다. 다양한 람다 표현식을 사용하려면 공통의 함수 디스크립터를 기술하는 함수형 인터페이스 집합이 필요하다.
자바 API는 Comparable, Runnable, Callable 등의 다양한 함수형 인터페이스를 포함하고 있다.
여기서 Java 8 라이브러리는 java.util.function 패키지에서 새로운 함수형 인터페이스를 제공하는데, 그 중에서 Predicate, Consumer, Function 인터페이스를 살펴보자.
Predicate
java.util.function.Predicate<T> 인터페이스는 test라는 추상 메서드를 정의하며 test는 제네릭 형식 T의 객체를 인수로 받아 boolean을 반환한다.
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
public class PredicateTest {
public static void main(String[] args) {
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(List.of("1", "", "3"), nonEmptyStringPredicate);
System.out.println(nonEmpty);
}
public static <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> results = new ArrayList<>();
for (T t : list) {
if (p.test(t)) {
results.add(t);
}
}
return results;
}
}
Consumer
java.util.function.Consumer<T> 인터페이스는 제네릭 형식 T 객체를 받아서 void를 반환하는 accept라는 추상 메서드를 정의한다.
T 형식의 객체를 인수로 받아서 어떤 동작을 수행하고 싶을 때 Consumer 인터페이스를 사용할 수 있다.
예를 들어, Integer 리스트를 인수로 받아서 각 항목에 어떤 동작을 수행하는 forEach 메서드를 정의할 때 Consumer를 활용할 수 있다.
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
public class ConsumerTest {
public static void main(String[] args) {
forEach(List.of(1, 2, 3, 4, 5),
(Integer i) -> System.out.println(i));
}
public static <T> void forEach(List<T> list, Consumer<T> c) {
for (T t : list) {
c.accept(t);
}
}
}
Function
java.util.function.Function<T, R> 인터페이스는 제네릭 형식 T를 인수로 받아서 제네릭 형식 R 객체를 반환하는 추상 메서드 apply를 정의한다.
입력을 출력으로 매핑하는 람다를 정의할 때 Function 인터페이스를 활용할 수 있다.
예를 들어, 사과의 무게 정보를 추출하거나 문자열을 길이와 매핑하는 동작을 수행할 때 활용할 수 있다.
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
public class FunctionTest {
public static void main(String[] args) {
List<Integer> l = map(Arrays.asList("lambdas", "in", "action"),
String::length);
System.out.println(l);
}
public static <T, R> List<R> map(List<T> list, Function<T, R> f) {
List<R> result = new ArrayList<>();
for (T t : list) {
result.add(f.apply(t));
}
return result;
}
}
기본형 특화
Predicate<T>, Consumer<T>, Function<T, R>을 살펴봤다. 하지만 이렇게 제네릭을 사용하면 참조형 타입만 사용할 수 있다.
물론 자바에서 자동으로 박싱, 언박싱을 수행해주는 오토박싱 기능을 제공하지만, 이는 수행에 있어 적지 않은 비용이 소모된다.
Java 8에서는 기본형을 입출력으로 사용하는 상황에서 오토박싱 동작을 피할 수 있도록 특별한 버전의 함수형 인터페이스를 제공한다.
예를 들어, Predicate<Integer>는 Integer로 박싱하지만 IntPredicate는 박싱을 하지 않는다.
@FunctionalInterface
public interface IntPredicate {
boolean test(int t);
}
이 외에도 DoublePredicate, ToIntFunction<T>, IntToDoubleFunction 등 여러가지 함수형 인터페이스를 지원한다.
'Language > Java' 카테고리의 다른 글
[Java] 메서드 참조 (0) | 2023.04.04 |
---|---|
[Java] 람다 타입 검사/추론/제약 (0) | 2023.04.04 |
[Java] 람다 표현식 소개 (0) | 2023.03.31 |
[Java] 동작 파라미터화 코드 전달하기 (0) | 2023.03.28 |
[Java] 바이트코드 조작 (0) | 2023.03.22 |
댓글
이 글 공유하기
다른 글
-
[Java] 메서드 참조
[Java] 메서드 참조
2023.04.04 -
[Java] 람다 타입 검사/추론/제약
[Java] 람다 타입 검사/추론/제약
2023.04.04 -
[Java] 람다 표현식 소개
[Java] 람다 표현식 소개
2023.03.31 -
[Java] 동작 파라미터화 코드 전달하기
[Java] 동작 파라미터화 코드 전달하기
2023.03.28