함수형 인터페이스
✒️ 2025-05-23 16:39 내용 수정
람다식을 사용할 때 참조하는 참조 변수의 타입
- 람다식 활용과 같이 참고.
- 상속을 따로 하는 것이 아니라 직접적으로 익명 클래스(내부 클래스(Inner classes)#4. 익명 클래스)화해서 사용하는 인터페이스
- 객체 지향 프로그램에서 인터페이스를 사용하려면 인터페이스를 클래스에서 구현한 뒤 사용해야 하지만, 람다식을 사용하면 이 과정을 생략할 수 있다.
- 일반 인터페이스와 추상 클래스는 메서드를 여러 개 정의할 수 있지만, 함수형 인터페이스는 한 개의 추상 메서드만 정의할 수 있다.
- 여기서 추상 메서드는
abstract키워드를 생략해도 되며, 자동으로public abstract로 간주된다.
- 여기서 추상 메서드는
- default 메서드는 기본 동작을 제공하고, 함수형 인터페이스의 규칙을 어기지 않는다.
- 즉 함수형 인터페이스에는 한 개의 추상 메서드와 여러 개의 default 메서드를 가진다.
@FunctionalInterface
public interface MyFunctionalInterface {
void execute(); // 유일한 추상 메서드
default void log(String message) {
System.out.println("Log: " + message);
}
static void print(String message) {
System.out.println(message);
}
}
- 어노테이션(annotation)
@FunctionalInterface을 사용해서 함수형 인터페이스라는 걸 알려줘야 한다. - 모든 함수(메서드)는 클래스 내부에 존재해야 해서, 함수 1개만 사용하고 싶다면 인터페이스에 함수 1개만 넣고 사용해야 한다.
java.util.function
- java.util.function 패키지에는 여러 상황에서 사용할 수 있는 다양한 함수형 인터페이스가 있다.
- Oracle java.util.function 문서 참고.
- 매번 함수형 인터페이스를 정의하기보다 패키지에 정의된 인터페이스를 활용하는 것이 좋다.
- 대부분의 메서드는 파라미터가 0~2개인 경우가 많고, 반환값이 0~1개인 경우가 많다.
- 제너릭 메서드로 정의하면 매개변수나 반환 타입이 달라져도 문제가 되지 않는다.
- 제너릭 참고
- Stream의 여러 메서드를 사용할 때 매개 변수로 함수형 인터페이스를 많이 사용했다
- 스트림의 연산 참고.
| 이름 | 메서드 | 특징 |
|---|---|---|
| Supplier | T get() |
매개변수 0, 반환값 O |
| Consumer | void accept(T t) |
매개변수 1, 반환값 X |
| Function<T,R> | R apply(T t) |
매개변수 1, 반환값 O |
| Predicate | boolean test(T t) |
매개변수 1, 반환값 항상 논리형 |
| BiConsumer<T,U> | void accept(T t, U u) |
매개변수 2, 반환값 X |
| BiPredicate<T,U> | boolean test(T t, U u) |
매개변수 2, 반환값 논리형 |
| BiFunction<T,U,R> | R apply<T t, U u> |
매개변수 2, 반환값 O |
@FunctionalInterface // 함수형 인터페이스 명시!
public interface CompareNumber { // 함수형 인터페이스!
int compareTo(int num01, int num02);
}
public CompareMain {
public static void main(String[] args) {
// 함수형 인터페이스의 타입으로 람다식!
CompareNumber compare = (num01, num02) -> {return x > y ? x : y};
System.out.println(compare.compareTo(num01, num02));
}
}
람다식의 합성과 결합
- 두 람다식을 합성하여 새로운 람다식을 만들 수 있다.
f.andThen(g): f를 먼저 적용하고 다음에 함수 g를 적용한다.f.compose(g): g를 먼저 적용하고 f를 적용한다.Function.identity(): 함수를 적용하기 이전과 동일한 항등 함수x -> x- 함수형 인터페이스 메서드 목록 참고
// Function<T,R>의 R apply<T t> 사용
import java.util.function.Function;
public class Ex2_function2 {
public static void main(String[] args) {
// String을 16진수의 int로 변환
Function<String, Integer> f = s -> Integer.parseInt(s, 16);
// int를 2진수로 바꾸어 String으로 변환
Function<Integer, String> g = i -> Integer.toBinaryString(i);
Function<String, String> h = f.andThen(g); // FF -> 255 -> 11111111
System.out.println(h.apply("FF"));
Function<Integer, Integer> h2 = f.compose(g); // 2 -> 10 -> 16
System.out.println(h2.apply(2));
Function<String, String> f2 = x -> x;
System.out.println(f2.apply("hello"));
}
}
11111111
16
hello
- Predicate의 결합
- Predicate를 and(), or(), negate()로 연결해서 하나의 새로운 Predicate로 만들 수 있다.
- f.and(g) : f와 g의 and 연산 결과
- f.or(g) : f와 g의 or 연산 결과
- f.negate() : f의 not 연산 결과
import java.util.function.Predicate;
public class Ex2_function2 {
public static void main(String[] args) {
Predicate<Integer> p = i -> i < 100;
Predicate<Integer> q = i -> i < 200;
Predicate<Integer> r = i -> i % 2 == 100;
Predicate<Integer> notP = p.negate(); // negate는 not이므로 i >= 100
Predicate<Integer> all = notP.and(q.or(r)); // i>=100 &&(i<200||i%2==0)
System.out.println(all.test(150));
}
}
true
람다식의 메서드 참조
- 메서드를 참조해서 매개변수의 정보 및 리턴 타입을 알아내어 람다식에서 불필요한 매개변수를 제거하는 것이다.
- 람다식이 하나의 메서드만 호출하는 경우 메서드 참조라는 방법으로 람다식을 간결하게 바꿀 수 있다.
클래스이름::메서드이름
참조변수::메서드이름
import java.util.function.BiFunction;
import java.util.function.Function;
public class Ex3_function3 {
public static void main(String[] args) {
//Function<String, Integer> f = (String s) -> Integer.parseInt(s);
Function<String, Integer> f = Integer::parseInt;
//BiFunction<String, String, Boolean> f2 = (s1, s2) -> s1.equals(s2);
BiFunction<String, String, Boolean> f2 = String::equals;
// 정적 메서드의 참조
IntBinaryOperator operator;
//operator = (x,y) -> Math.max(x,y);
operator = Math::max;
System.out.println(operator.applyAsInt(10, 20));
}
}
20