함수형 인터페이스에 제네릭을 넣어 필요한 타입에 맞춰 사용할 수 있다. 그러면 함수형 인터페이스 자체의 재사용성이 높아진다.
@FunctionalInterface
interface GenericFunction<T, R> {
R apply(T s);
}
람다는 인스턴스는 가지고 있지만, 자체에 타겟 타입은 없으므로 참조가 타입이 된다. 따라서 같은 시그니처라도 타입이 다르면 대입해서 사용할 수 없다.
타입이 다르면 사용할 수 없다는 점 때문에 통일시키기 위해 자바에서는 기본 함수형 인터페이스를 제공해준다.
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
말그대로 함수다. 기본적인 함수형 인터페이스로, 함수를 적용해서 특정한 값을 얻어낸다.
package java.util.function;
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
이름 그대로 값을 입력만 받고 반환은 안 해주는 인터페이스다. 그래서 매개변수 타입만 있고 반환 타입은 없다. 주로 로그 출력이나 저장하는 등 내부적으로 저장할 때 사용한다.
package java.util.function;
@FunctionalInterface
public interface Supplier<T> {
T get();
}
공급, 즉 반환만 해주는 인터페이스다. Consumer와 달리 반환 타입만 존재한다. 주로 랜덤 값 출력 같은 값 생성이나 지연 초기화 등에 사용된다.
package java.lang;
@FunctionalInterface
public interface Runnable {
void run();
}
반환도, 매개변수도 타입이 없는 함수형 인터페이스다. 주로 멀티 스레드 상황에서 사용한다. 당연히 람다도 사용할 수 있다.
어떤 특정 상황에 특화된 인터페이스들이다.
package java.util.function;
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
참, 거짓을 판별할 때 사용하는 인터페이스다. 그래서 밚한 타입이 boolean이다. 값이 어떤 조건에 맞는지 조건 검사나 필터링에 사용된다.
반환 타입과 매개변수 타입 똑같은 인터페이스다. 특정한 값을 넣어 똑같은 값을 산출해야 할 때 사용해야 한다.
package java.util.function;
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
T apply(T t); // 실제 코드가 있지는 않음
}
매개변수 한 개인 인터페이스다. 한 개의 값을 넣어 한 개의 결과를 산출하는 단항 연산을 한다.
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
T apply(T t1, T t2); // 실제 코드가 있지는 않음
}
두 개의 매개변수를 넣어 계산하는 다항 연산 인터페이스다.
Function, BiFunction으로 충분히 두 인터페이스의 기능을 구현할 수 있음에도 필요한 이유가 뭘까. 그건 명시성과 가독성, 유지보수성을 위해서다. 각각 '같은 값을 같은 값을 반환할 것'과 '조건으로 검사하거나 필터링할 것'임을 알려주기 때문이다. 이러면 Function에 비해 이 함수는 어떤 기능을 할지 훨씬 명확히 보인다.
매개변수 2개일 때 bi가 붙은 인터페이이스를 사용하면된다. BiFiction, BiConsumer, BiSupplier 등 다양한 인터페이스가 있다.
매개변수가 3개일 때는 직접 만들어서 사용해야 한다. 다만, 3개인 경우는 잘 없다.
타입에 기본형을 써야할 때르 위해 자바에서는 기본형을 지원하는 인터페이스를 제공한다. 다만 이건 다 외우기보다는 앞에 Int, ToInt 같은 기본형 이름이 붙기 때문에 이를 통해 필요할 때마다 찾아보는 게 좋다.