CodeGym /Cursos /JAVA 25 SELF /Interfaces funcionales: @FunctionalInterface

Interfaces funcionales: @FunctionalInterface

JAVA 25 SELF
Nivel 20, Lección 3
Disponible

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
Runnable
void run()
Tarea sin argumentos ni resultado Hilos, temporizadores
Callable<V>
V call()
Tarea con resultado Hilos, ExecutorService
Comparator<T>
int compare(T, T)
Comparación de dos objetos Ordenación de colecciones
Consumer<T>
void accept(T)
«Consumidor» de un valor Recorrido de una colección
Supplier<T>
T get()
«Proveedor» de un valor Inicialización perezosa, generación de datos
Function<T, R>
R apply(T)
Función de T a R Transformación de datos
Predicate<T>
boolean test(T)
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.

1
Tarea
JAVA 25 SELF, nivel 20, lección 3
Bloqueada
Tu notificador personal
Tu notificador personal
1
Tarea
JAVA 25 SELF, nivel 20, lección 3
Bloqueada
Comprobador del juego: Puntuaciones altas
Comprobador del juego: Puntuaciones altas
1
Tarea
JAVA 25 SELF, nivel 20, lección 3
Bloqueada
Validador de Texto: Comprobación de la Longitud de la Cadena
Validador de Texto: Comprobación de la Longitud de la Cadena
1
Tarea
JAVA 25 SELF, nivel 20, lección 3
Bloqueada
Convertidor de Texto con Salida Conveniente
Convertidor de Texto con Salida Conveniente
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION