1. Introducción
Una interfaz funcional es una interfaz que contiene exactamente un método abstracto (es decir, un método sin implementación). Precisamente este tipo de interfaz puede utilizarse para una forma abreviada de implementar un método — en Java esto se hace mediante expresiones lambda (las estudiaremos más adelante).
¿Por qué solo un método?
Porque una interfaz funcional describe exactamente una operación. Si hubiera dos o más métodos, no estaría claro cuál hay que implementar. Por eso la regla es simple: una interfaz — un método abstracto.
Ejemplos de la biblioteca estándar
- Runnable — para tareas en hilos (void run())
- Callable<V> — para tareas que devuelven un resultado (V call())
- Comparator<T> — para comparar objetos (int compare(T o1, T o2))
- Consumer<T> — «consumidor» de un valor (void accept(T t))
- Supplier<T> — «proveedor» de un valor (T get())
- Function<T, R> — función de T a R (R apply(T t))
- Predicate<T> — comprobación de una condición (boolean test(T t))
He aquí, por ejemplo, la interfaz Runnable:
public interface Runnable {
void run();
}
Y aquí la interfaz Comparator:
public interface Comparator<T> {
int compare(T o1, T o2);
// ... también hay métodos default y static, ¡pero solo hay un método abstracto!
}
Punto importante: los métodos default y static no se consideran abstractos, ¡por lo que puede haber tantos como se quiera!
2. La anotación @FunctionalInterface
Java es una dama estricta y de principios. Para evitar confusiones, permite marcar explícitamente una interfaz como funcional con la anotación @FunctionalInterface. Es como una pegatina de «¡Funciona solo con un botón!» — para que nadie añada nada de más.
@FunctionalInterface
public interface Operation {
int apply(int a, int b);
}
Ahora, si por descuido añades un segundo método abstracto, el compilador te avisará inmediatamente de un error:
@FunctionalInterface
public interface Oops {
void doIt();
void doSomethingElse(); // ¡Error! Dos métodos abstractos
}
¿Es obligatoria la anotación?
No, no es obligatorio. La interfaz será funcional incluso sin ella, si contiene exactamente un método abstracto. Pero con la anotación dejas clara tu intención y te proteges de errores accidentales.
¿Se pueden añadir métodos default y static?
¡Sí, se puede! Lo principal es que haya solo uno método abstracto. El resto de métodos pueden ser default o static, tantos como quieras.
Ejemplo:
@FunctionalInterface
public interface FancyOperation {
int apply(int a, int b);
default void printInfo() {
System.out.println("¡Soy una operación 'fancy'!");
}
static void description() {
System.out.println("Interfaz funcional para aritmética.");
}
}
3. Ejemplos de declaración y uso
Supongamos que quieres describir una operación sobre dos números. Así se hace:
@FunctionalInterface
public interface Operation {
int apply(int a, int b);
}
Ahora esta interfaz se puede implementar de varias maneras.
Implementación mediante una clase normal
public class SumOperation implements Operation {
@Override
public int apply(int a, int b) {
return a + b;
}
}
Uso:
Operation sum = new SumOperation();
System.out.println(sum.apply(2, 3)); // 5
Implementación mediante una clase anónima
Operation multiply = new Operation() {
@Override
public int apply(int a, int b) {
return a * b;
}
};
System.out.println(multiply.apply(2, 3)); // 6
Nota sobre las lambdas
A partir de Java 8, este tipo de interfaces se implementan cómodamente con expresiones lambda — una sintaxis más corta. Estudiaremos las lambdas dentro de un par de lecciones, así que por ahora basta con saber que las interfaces funcionales están pensadas precisamente para trabajar con ellas de la forma más cómoda posible.
4. Práctica: escribe tu propia interfaz funcional
Tarea 1. ¡Crea tu Action!
Crea una interfaz Action que reciba una cadena y no devuelva nada. Impleméntala mediante una clase anónima que imprima la cadena en mayúsculas.
@FunctionalInterface
interface Action {
void act(String s);
}
public class ActionDemo {
public static void main(String[] args) {
Action shout = new Action() {
@Override
public void act(String text) {
System.out.println(text.toUpperCase());
}
};
shout.act("¡estoy aprendiendo java!"); // ¡ESTOY APRENDIENDO JAVA!
}
}
(Más adelante veremos cómo escribirlo de forma más concisa con expresiones lambda.)
Tarea 2. Filtro de números
Crea la interfaz NumberPredicate con el método boolean test(int n). Implementa la comprobación de paridad mediante una clase anónima.
@FunctionalInterface
interface NumberPredicate {
boolean test(int n);
}
public class PredicateDemo {
public static void main(String[] args) {
NumberPredicate isEven = new NumberPredicate() {
@Override
public boolean test(int n) {
return n % 2 == 0;
}
};
System.out.println(isEven.test(4)); // true
System.out.println(isEven.test(7)); // false
}
}
Tarea 3. Usemos interfaces estándar
En lugar de tu propia interfaz, puedes usar la ya existente Predicate<Integer>:
import java.util.function.Predicate;
Predicate<Integer> isPositive = new Predicate<Integer>() {
@Override
public boolean test(Integer x) {
return x > 0;
}
};
System.out.println(isPositive.test(10)); // true
System.out.println(isPositive.test(-5)); // false
Tabla: interfaces funcionales de la biblioteca estándar
| Interfaz | Método | Descripción | Ejemplo de uso |
|---|---|---|---|
|
|
Tarea sin argumentos ni resultado | Hilos, temporizadores |
|
|
Tarea con resultado | Hilos, ExecutorService |
|
|
Comparación de dos objetos | Ordenación de colecciones |
|
|
«Consumidor» de un valor | Recorrido de una colección |
|
|
«Proveedor» de un valor | Inicialización perezosa, generación de datos |
|
|
Función de T a R | Transformación de datos |
|
|
Comprobación de una condición | Filtrado de colecciones |
5. Errores típicos al trabajar con interfaces funcionales
Error n.º 1: añadiste un segundo método abstracto. Si una interfaz tiene más de un método abstracto, deja de ser funcional. El compilador (sobre todo con @FunctionalInterface) lo señalará como error al instante.
Error n.º 2: olvidaste que los métodos default y static no se consideran abstractos. Puedes añadirlos sin miedo a una interfaz funcional: no violarás la regla de «un único método abstracto».
Error n.º 3: implementaste incorrectamente la firma del método. Por ejemplo, la interfaz requiere dos argumentos y escribiste un método con solo uno. Revisa siempre las firmas.
Error n.º 4: no usas @FunctionalInterface y estropeas la interfaz por accidente. Si no marcas la interfaz con la anotación, puedes añadir por accidente un método de más y luego pasarte tiempo buscando por qué el código no funciona. Es mejor añadir siempre la anotación para dejar clara la intención.
GO TO FULL VERSION