Las interfaces funcionales son todas aquellas interfaces que definen un único método abstracto, pudiendo implementar uno o varios métodos default o static.
Este nuevo tipo de interfaces son especialmente importantes debido a que son la base de la implementación de las nuevas expresiones lambda, una de las funcionalidades más importantes de Java 8.
A continuación podemos ver un ejemplo de interfaz funcional, en la que se define un único método abstracto, y varios métodos default y static:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| public interface Calculator {
// My abstract method
String calculate(int arg1, int arg2);
// Default method
default int sum(int arg1, int arg2) {
return arg1 + arg2;
}
// Static method
static int floatToInt(float arg) {
return Math.round(arg);
}
}
|
Para asegurarnos de que nuestra interfaz funcional está bien implementada, Java 8 ha incluido una anotación que nos permite verificarlo, para ello deberemos incluir la anotación @FunctionalInterface a nivel de la clase.
Esta anotación es procesada por nuestro IDE, y nos marcará si la interfaz está correctamente implementada.
1
2
3
4
5
| @FunctionalInterface
public interface Calculator {
// My abstract method
String calculate(int arg1, int arg2);
}
|
Ejemplos de interfaces funcionales incluidas en Java 8
Java 8 incluye múltiples interfaces funcionales que podemos utilizar para crear expresiones lambda, y que son muy utilizadas en la API de Streams de Java. Podemos encontrar estas interfaces en el paquete java.util.function.
Sin entrar en cada una de ellas, a continuación os dejo ejemplos de las interfaces nuevas que se han incluido en la versión de Java 8.
Interfaces Consumer y BiConsumer<T,U>
Las interfaces Consumer y BiConsumer representan una operación que recibe uno y dos valores respectivamente, y no devuelven ningún resultado.
1
2
3
4
5
6
7
8
9
| @FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
|
1
2
3
4
5
6
7
8
9
| @FunctionalInterface
public interface BiConsumer<T,U> {
void accept(T t, U u);
default BiConsumer<T,U> andThen(BiConsumer<? super T,? super U> after) {
Objects.requireNonNull(after);
return (T t, U u) -> { accept(t,u); after.accept(t,u); };
}
}
|
Interfaz Supplier
La interfaz Supplier representa un proveedor de resultados, sin parámetros de entrada y con un parámetro de salida.
1
2
3
4
| @FunctionalInterface
public interface Supplier<T> {
T get();
}
|
Interfaces Function y BiFunction<T,U>
Las interfaces Function y BiFunction representan una función que recibe uno y dos valores respectivamente, y produce un valor como resultado.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| @FunctionalInterface
public interface Function<T,R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
|
1
2
3
4
5
6
7
8
9
| @FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
|
Interfaz UnaryOperator
La interfaz UnaryOperator representa una operación en la cual se recibe un parámetro de entrada, y se devuelve un valor del mismo tipo como resultado.
1
2
3
4
5
6
| @FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
static <T> UnaryOperator<T> identity() {
return t -> t;
}
}
|
Interfaz BinaryOperator
La interfaz BinaryOperator representa una operación en la cual se reciben dos parámetro de entrada del mismo tipo, y se devuelve un valor del mismo tipo como resultado.
1
2
3
4
5
6
7
8
9
10
11
12
| @FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
}
|
Interfaces Predicate y BiPredicate
Las interfaces Predicate y BiPredicate representan funciones que devuelven un booleano como resultado, y reciben uno y dos parámetros de entrada respectivamente.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| @FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| @FunctionalInterface
public interface BiPredicate<T, U> {
boolean test(T t, U u);
default BiPredicate<T, U> and(BiPredicate<? super T, ? super U> other) {
Objects.requireNonNull(other);
return (T t, U u) -> test(t, u) && other.test(t, u);
}
default BiPredicate<T, U> negate() {
return (T t, U u) -> !test(t, u);
}
default BiPredicate<T, U> or(BiPredicate<? super T, ? super U> other) {
Objects.requireNonNull(other);
return (T t, U u) -> test(t, u) || other.test(t, u);
}
}
|