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 |
|---|---|---|---|
|
|
Tarefa sem argumentos e sem resultado | Threads, temporizadores |
|
|
Tarefa com resultado | Threads, ExecutorService |
|
|
Comparação de dois objetos | Ordenação de coleções |
|
|
“Consumidor” de um valor | Iteração de coleções |
|
|
“Fornecedor” de um valor | Inicialização preguiçosa, geração de dados |
|
|
Função de T para R | Transformação de dados |
|
|
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.
GO TO FULL VERSION