W3docs

Interfaces Funcionais em Java

Interfaces com um único método abstrato em Java que servem como alvos para lambdas, marcadas com @FunctionalInterface.

Uma interface funcional é uma interface com exatamente um método abstrato. Esse único método é o alvo para o qual uma lambda ou referência de método é compilada. Runnable, Comparator<T>, Callable<V>, Supplier<T>, Function<T, R>, Predicate<T>, Consumer<T>, ActionListener, FileFilter — todas são interfaces funcionais. Há dezenas já no JDK e você escreverá as suas próprias quando nenhuma delas se encaixar.

O capítulo anterior mostrou lambdas como () -> 42 e s -> s.length() "compilando para qualquer interface que o contexto precisar." Este capítulo responde o que torna uma interface um alvo válido — a regra do método abstrato único (SAM) — e como @FunctionalInterface permite dizer "sim, esta é uma, e quero que o compilador a imponha."

A regra SAM, com precisão

Para ser funcional, uma interface deve declarar exatamente um método que precisa de implementação. A formulação importa: não "exatamente um método no total," mas "exatamente um método abstrato." Três categorias de métodos não contam contra o único:

  1. Métodos default — eles já têm um corpo, portanto um implementador não precisa fornecer um.
  2. Métodos static — pertencem à própria interface, não aos implementadores.
  3. Métodos abstratos public que sobrescrevem um método em java.lang.Object — por exemplo, equals, hashCode, toString. Toda classe já herda implementações de Object, então redeclará-los em uma interface não adiciona um novo requisito.

O terceiro surpreende as pessoas. Comparator<T> declara boolean equals(Object), mas ainda é funcional porque esse método vem de Object. O método abstrato real é int compare(T, T).

@FunctionalInterface
interface MyComparator<T> {
  int compare(T a, T b);                          // the one SAM
  boolean equals(Object other);                   // Object override — doesn't count
  default MyComparator<T> reversed() {            // default — doesn't count
    return (a, b) -> compare(b, a);
  }
  static <T extends Comparable<T>> MyComparator<T> natural() {   // static — doesn't count
    return (a, b) -> a.compareTo(b);
  }
}

@FunctionalInterface — verificação em tempo de compilação opcional

A anotação é opcional. Uma interface é funcional com base em sua forma, não em ser anotada. Mas anotar traz dois benefícios:

  1. Erro de compilação se a interface deixar de ser funcional. Adicione acidentalmente um segundo método abstrato e o compilador o interromperá imediatamente — na interface, não em cada ponto de chamada que a usa como alvo de lambda.
  2. Documentação. A anotação sinaliza "esta interface foi criada para ser usada como alvo de lambda," o que vale dizer em qualquer caso não óbvio.
@FunctionalInterface
interface Validator<T> {
  boolean isValid(T value);
  boolean isInvalid(T value);     // <-- compile error: not a functional interface
}

Sem a anotação, esse segundo método silenciosamente tornaria Validator<T> uma interface não funcional, e o primeiro ponto de chamada com estilo lambda que a usasse falharia ao compilar com uma mensagem confusa longe da causa.

A anotação também é a convenção para as próprias interfaces funcionais do JDK — Function, Predicate, Consumer, Supplier, Runnable, Callable — todas a carregam.

Lambdas, referências de métodos e classes anônimas são intercambiáveis

Uma interface funcional aceita três tipos de valor, e eles são livremente intercambiáveis:

Predicate<String> blank1 = s -> s.trim().isEmpty();               // lambda
Predicate<String> blank2 = String::isBlank;                        // method reference (since Java 11)
Predicate<String> blank3 = new Predicate<>() {                    // anonymous class
  @Override public boolean test(String s) { return s.trim().isEmpty(); }
};

Os três implementam a mesma interface Predicate<String> e produzem valores equivalentes no ponto de chamada. A lambda e a referência de método são dramaticamente mais curtas; a classe anônima é reservada para os casos raros listados no capítulo anterior (mais de um método necessário, estado local ao método, this referindo-se à nova instância).

Interfaces funcionais genéricas

A interface pode ser parametrizada — é assim que uma única declaração de Function<T, R> pode ser usada para toda transformação:

@FunctionalInterface
interface Mapper<T, R> {
  R map(T input);
}

Mapper<String, Integer> length = s -> s.length();
Mapper<Integer, String> hex    = n -> Integer.toHexString(n);

Os parâmetros podem ser limitados, podem incluir múltiplas variáveis de tipo e podem ser reutilizados entre interfaces — a biblioteca padrão usa todas as variações.

Escrevendo sua própria interface funcional

Na maioria das vezes você deve usar as interfaces integradas em java.util.function — o próximo capítulo as apresenta todas. Escreva a sua própria quando:

  • A semântica merece um nome. Validator<T> é mais legível em um ponto de chamada do que Function<T, ValidationResult>, mesmo que a forma corresponda.
  • Você precisa de uma exceção verificada. Function.apply não lança nada verificado; se sua operação lança IOException, escreva um SAM que a declare.
  • A forma não está na biblioteca padrão. Um método que recebe três argumentos (uma função tri) não tem interface integrada — escreva uma quando precisar.
@FunctionalInterface
interface IOFunction<T, R> {
  R apply(T input) throws IOException;
}

IOFunction<Path, String> readAll = Files::readString;       // declared exception — built-in Function can't

Uma quantidade surpreendentemente grande de "devo escrever isso?" se resume a legibilidade ou propagação de exceções.

Métodos default valem o esforço

O lugar onde você escreverá sua própria interface funcional e também adicionará métodos default é quando quiser que os chamadores possam compor instâncias:

@FunctionalInterface
interface Filter<T> {
  boolean keep(T value);

  default Filter<T> and(Filter<T> other) {
    return v -> keep(v) && other.keep(v);
  }
  default Filter<T> negate() {
    return v -> !keep(v);
  }
}

Filter<Integer> positive = n -> n > 0;
Filter<Integer> even     = n -> n % 2 == 0;
Filter<Integer> posOdd   = positive.and(even.negate());

Essa é exatamente a receita que o JDK usa para Predicate.and / or / negate, Function.andThen / compose e Comparator.thenComparing. O método abstrato único é o comportamento; os métodos default são a álgebra de composição que o rodeia.

Um exemplo completo: escrevendo, anotando e compondo

O programa abaixo define uma interface funcional Filter<T> com dois métodos default, demonstra a regra SAM (um método abstrato extra não compilaria) e mostra lambdas, referências de métodos e uma classe anônima, todos implementando o mesmo SAM.

java— editable, runs on the server

O que observar na execução:

  • notBlank1 (lambda), notBlank2 (cadeia de referência de método) e notBlank3 (classe anônima) implementam a mesma interface Filter<String> — de forma intercambiável. A lambda é a mais curta; a classe anônima é reservada para casos que lambdas não conseguem tratar.
  • positive.and(even.negate()) compôs três filtros em um sem declarações de métodos extras. Os métodos default and e negate na interface são a álgebra de composição — é por isso que o JDK os adiciona a Predicate, Function e Comparator também.
  • SafelyFunctional<T> declara tanto apply(T) quanto boolean equals(Object), e ainda assim compilou com @FunctionalInterface. A sobrescrita de equals é herdada de Object, portanto não conta contra a regra do método abstrato único.
  • Se você remover uma palavra-chave default em Filter (transformando um default em um segundo método abstrato), a anotação @FunctionalInterface força um erro de compilação imediato na declaração da interface — muito antes de qualquer ponto de chamada lambda ver falhas de inferência confusas.

O que vem a seguir

Você já sabe reconhecer uma interface funcional, escrever uma quando o JDK não tem o que você precisa e deixar o compilador impor sua forma. Quase sempre, porém, a resposta certa é "use o que já existe." O próximo capítulo, Interfaces Funcionais Integradas do Java, percorre java.util.functionFunction, Predicate, Consumer, Supplier, suas variantes bi e as especializações primitivas que existem para evitar boxing.

Prática

Prática
Uma interface declara três métodos: um método abstrato, um método `default` e `boolean equals(Object)` redeclarado de `Object`. É uma `@FunctionalInterface` válida?
Uma interface declara três métodos: um método abstrato, um método `default` e `boolean equals(Object)` redeclarado de `Object`. É uma `@FunctionalInterface` válida?
Was this page helpful?