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

Interfaces funcionais: @FunctionalInterface

JAVA 25 SELF
Nível 20 , Lição 3
Disponível

1. Introdução

Interface funcional — é uma interface que contém exatamente um método abstrato (ou seja, um método sem implementação). É precisamente esse tipo de interface que pode ser usado para uma forma concisa de implementar um método — em Java isso é feito com expressões lambda (vamos estudá-las mais adiante).

Por que apenas um método?

Porque uma interface funcional descreve exatamente uma operação. Se houvesse dois ou mais métodos, ficaria indefinido qual método implementar. Portanto, a regra é simples: uma interface — um método abstrato.

Exemplos da biblioteca padrão

  • Runnable — para tarefas em threads (void run())
  • Callable<V> — para tarefas que retornam resultado (V call())
  • Comparator<T> — para comparação de objetos (int compare(T o1, T o2))
  • Consumer<T> — “consumidor” de um valor (void accept(T t))
  • Supplier<T> — “fornecedor” de um valor (T get())
  • Function<T, R> — função de T para R (R apply(T t))
  • Predicate<T> — verificação de condição (boolean test(T t))

Aqui está, por exemplo, a interface Runnable:

public interface Runnable {
    void run();
}

E aqui a interface Comparator:

public interface Comparator<T> {
    int compare(T o1, T o2);
    // ... há também métodos default e static, mas o método abstrato é apenas um!
}

Ponto importante: métodos default e static não são considerados abstratos, portanto pode haver quantos você quiser!

2. Anotação @FunctionalInterface

Java é uma senhora rigorosa e exigente. Para evitar confusão, ela permite marcar explicitamente uma interface como funcional com a anotação @FunctionalInterface. É como um adesivo “Funciona apenas com um botão!” — para que ninguém acrescente algo indevido.

@FunctionalInterface
public interface Operation {
    int apply(int a, int b);
}

Agora, se você por acaso esquecer e adicionar um segundo método abstrato, o compilador imediatamente informará um erro:

@FunctionalInterface
public interface Oops {
    void doIt();
    void doSomethingElse(); // Erro! Dois métodos abstratos
}

A anotação é obrigatória?

Não, não é obrigatório. A interface será funcional mesmo sem ela, se contiver exatamente um método abstrato. Mas com a anotação você deixa explícita a sua intenção e se protege de erros acidentais.

Posso adicionar métodos default e static?

Sim! O principal é haver apenas um método abstrato. Todos os demais métodos podem ser default ou static, quantos quiser.

Exemplo:

@FunctionalInterface
public interface FancyOperation {
    int apply(int a, int b);

    default void printInfo() {
        System.out.println("Eu sou uma operação fancy!");
    }

    static void description() {
        System.out.println("Interface funcional para aritmética.");
    }
}

3. Exemplos de declaração e uso

Suponha que você queira descrever uma operação sobre dois números. É assim que se faz:

@FunctionalInterface
public interface Operation {
    int apply(int a, int b);
}

Agora essa interface pode ser implementada de várias maneiras.

Implementação via classe comum

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

Implementação via classe 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

Observação sobre lambdas

A partir do Java 8, tais interfaces são convenientes de implementar com expressões lambda — uma sintaxe mais curta. Vamos estudar lambdas em algumas aulas; por enquanto, basta saber: as interfaces funcionais foram criadas justamente para trabalhar com elas da forma mais conveniente.

4. Prática: escreva sua própria interface funcional

Tarefa 1. Crie seu Action!

Crie a interface Action, que recebe uma string e não retorna nada. Implemente-a por meio de uma classe anônima que imprime a string em maiú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("eu estudo java!"); // EU ESTUDO JAVA!
    }
}

(Mais adiante veremos como escrever isso de forma mais curta com expressões lambda.)

Tarefa 2. Filtro de números

Crie a interface NumberPredicate com o método boolean test(int n). Implemente a verificação de paridade usando uma classe 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
    }
}

Tarefa 3. Usando interfaces padrão

Em vez de criar sua própria interface, você pode usar a pronta 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

Tabela: interfaces funcionais da biblioteca padrão

Interface Método Descrição Exemplo de uso
Runnable
void run()
Tarefa sem argumentos e sem resultado Threads, temporizadores
Callable<V>
V call()
Tarefa com resultado Threads, ExecutorService
Comparator<T>
int compare(T, T)
Comparação de dois objetos Ordenação de coleções
Consumer<T>
void accept(T)
“Consumidor” de um valor Iteração de coleções
Supplier<T>
T get()
“Fornecedor” de um valor Inicialização preguiçosa, geração de dados
Function<T, R>
R apply(T)
Função de T para R Transformação de dados
Predicate<T>
boolean test(T)
Verificação de condição Filtragem de coleções

5. Erros comuns ao trabalhar com interfaces funcionais

Erro nº 1: foi adicionado um segundo método abstrato. Se houver mais de um método abstrato na interface, ela deixa de ser funcional. O compilador (especialmente com @FunctionalInterface) avisará imediatamente sobre o erro.

Erro nº 2: esquecer que métodos default e static não são considerados abstratos. Você pode adicioná-los sem medo a uma interface funcional — isso não viola a regra de “um método abstrato”.

Erro nº 3: implementação incorreta da assinatura do método. Por exemplo, a interface exige dois argumentos, mas você escreveu um método com apenas um. Sempre verifique as assinaturas.

Erro nº 4: não usar @FunctionalInterface e estragar a interface por acidente. Se você não marcar a interface com a anotação, pode acabar adicionando um método extra sem querer — e depois perder tempo para descobrir por que o código não funciona. É melhor sempre adicionar a anotação para deixar claro.

Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION