W3docs

Interfaces Funcionais Integradas do Java

O pacote java.util.function — Function, Predicate, Consumer, Supplier e suas variantes especializadas.

O pacote java.util.function foi incluído no Java 8 para fornecer ao JDK — e ao seu código — um vocabulário compartilhado para lambdas. Sem ele, cada método que aceitasse uma função precisaria definir sua própria interface pontual (StringMapper, IntToBool, RowHandler, …), e lambdas definidas para uma não poderiam ser reutilizadas para outra. O pacote resolve isso com 43 pequenas interfaces que cobrem os formatos que surgem repetidamente: "receber uma coisa e retornar outra", "receber uma coisa e decidir sim ou não", "receber uma coisa e fazer algo", "me dar uma coisa".

Se você aprender apenas quatro interfaces deste pacote, aprenda Function, Predicate, Consumer e Supplier. Quase tudo mais é uma variante de uma delas — versões de dois argumentos, especializações primitivas para evitar boxing, ou auxiliares de composição.

As quatro principais

Function<T, R>  f = t -> ...;       // takes a T, returns an R          — r = f.apply(t)
Predicate<T>    p = t -> ...;       // takes a T, returns a boolean      — boolean b = p.test(t)
Consumer<T>     c = t -> { ... };   // takes a T, returns nothing        — c.accept(t)
Supplier<T>     s = () -> ...;      // takes nothing, returns a T        — t = s.get()

Cada uma é anotada com @FunctionalInterface e possui um método abstrato de uma palavra (apply, test, accept, get). Raramente você chamará esses métodos diretamente quando streams estão envolvidos — stream().filter(predicate).map(function).forEach(consumer) faz as chamadas por você — mas conhecer o nome do método importa quando você escreve código que recebe um Function<T, R> como parâmetro e precisa invocá-lo.

Os formatos se mapeiam em perguntas comuns:

PerguntaInterface
"Transformar um X em um Y?"Function<X, Y>
"Este X é bom?"Predicate<X>
"Fazer algo com este X"Consumer<X>
"Me dê um X"Supplier<X>

Variantes de dois argumentos

Quando a operação precisa de dois argumentos, adicione o prefixo Bi:

BiFunction<T, U, R>     f = (t, u) -> ...;     // two ins, one out                    — apply
BiPredicate<T, U>       p = (t, u) -> ...;     // two ins, a boolean                  — test
BiConsumer<T, U>        c = (t, u) -> { ... }; // two ins, no out                     — accept

Não existe BiSupplierSupplier não recebe argumentos por definição, então um "supplier de dois argumentos" seria apenas uma BiFunction.

As variantes Bi são exatamente o que Map.forEach((k, v) -> ...), Map.merge e Map.compute esperam:

Map<String, Integer> scores = new HashMap<>();
scores.forEach((name, score) -> System.out.println(name + "=" + score));   // BiConsumer
scores.merge("alice", 1, Integer::sum);                                       // BinaryOperator<Integer>

BinaryOperator<T> é uma BiFunction<T, T, T> — mesmo tipo para as duas entradas e a saída. UnaryOperator<T> é similarmente uma Function<T, T>.

Especializações primitivas — evitando o custo de boxing

Function<Integer, Integer> funciona, mas toda chamada encapsula a entrada e encapsula o resultado em objetos. Em um loop intensivo, isso tem um custo real. O pacote, portanto, oferece versões especializadas para primitivos:

IntFunction<R>           f = i -> ...;        // int in, R out
IntPredicate             p = i -> ...;        // int in, boolean out
IntConsumer              c = i -> { ... };    // int in, void
IntSupplier              s = () -> 42;        // void in, int out
IntUnaryOperator         u = i -> i * 2;      // int in, int out
IntBinaryOperator        b = (a, c2) -> a + c2;

ToIntFunction<T>         f1 = t -> t.hashCode();         // T in, int out
ToIntBiFunction<T, U>    f2 = (t, u) -> t.hashCode() + u.hashCode();

IntToLongFunction        f3 = i -> (long) i * i;          // int in, long out
IntToDoubleFunction      f4 = i -> Math.sqrt(i);

A mesma família existe para Long e Double. A convenção de nomenclatura lê como uma frase:

  • IntX — opera sobre um int.
  • ToIntX — produz um int.
  • IntToLongXint de entrada, long de saída.

Em código com streams, mapToInt(...) retorna um IntStream, cujas operações terminais (sum, average, min, max) retornam primitivos sem boxing — o que é um dos maiores ganhos práticos das variantes primitivas.

Composição integrada nas interfaces

A maioria das interfaces vem com métodos default que permitem compor sem escrever novas lambdas:

// Function: andThen (left-to-right), compose (right-to-left)
Function<String, String>  trim  = String::trim;
Function<String, Integer> len   = String::length;
Function<String, Integer> trimLen = trim.andThen(len);          // trim, then length
Function<String, Integer> sameThing = len.compose(trim);          // length applied after trim

// Predicate: and / or / negate
Predicate<String> notNull  = Objects::nonNull;
Predicate<String> notBlank = s -> !s.trim().isEmpty();
Predicate<String> useful   = notNull.and(notBlank);
Predicate<String> blank    = notBlank.negate();

// Consumer: andThen (run two consumers in sequence)
Consumer<String> log   = System.out::println;
Consumer<String> save  = s -> writeToFile(s);
Consumer<String> both  = log.andThen(save);

// Comparator (in java.util, not java.util.function, but the same idea):
Comparator<Person> byName = Comparator.comparing(Person::name);
Comparator<Person> ordered = byName.thenComparing(Person::age);

Existe também uma fábrica estática útil: Predicate.not(p) é um atalho para p.negate() e lê de forma mais natural em um ponto de chamada:

list.removeIf(Predicate.not(String::isBlank));    // remove all blank strings

Function.identity e Predicate.isEqual — os pequenos estáticos úteis

Dois métodos de fábrica que você verá em código com streams e deve reconhecer:

Function<T, T> id = Function.identity();          // t -> t — useful as a no-op map

Predicate<Object> isFoo = Predicate.isEqual("foo");  // o -> Objects.equals(o, "foo")

Function.identity() é mais frequentemente usado como mapeador de chave ou valor em Collectors.toMap:

Map<String, Person> byName = people.stream()
    .collect(Collectors.toMap(Person::name, Function.identity()));

Predicate.isEqual raramente é mais curto do que s -> s.equals("foo"), mas compara com segurança para null usando Objects.equals, o que importa quando o stream pode conter null.

Um exemplo prático: as quatro principais, composição e especialização primitiva

O programa abaixo usa Function, Predicate, Consumer e Supplier, compõe alguns deles e contrasta um Function<Integer, Integer> (com boxing) com um IntUnaryOperator (primitivo) somando uma pequena lista.

java— editable, runs on the server

O que extrair da execução:

  • As quatro interfaces principais mapeiam de forma limpa em quatro tipos de trabalho: transformar (Function), testar (Predicate), agir (Consumer), produzir (Supplier). Os nomes dos seus métodos abstratos (apply, test, accept, get) valem a pena memorizar.
  • trim.andThen(length) e notNull.and(notBlank) construíram novos valores a partir dos antigos sem declarações de métodos auxiliares. Essa é a álgebra de composição que as interfaces carregam como métodos default.
  • O Function<Integer, Integer> com boxing é significativamente mais lento que o IntUnaryOperator primitivo porque cada chamada aloca dois objetos Integer. Em caminhos críticos — pipelines de stream que processam milhões de valores — as especializações primitivas valem o esforço.
  • Predicate.not(notBlank) lê de forma mais natural do que notBlank.negate() em um ponto de chamada removeIf. Ambos compilam para a mesma coisa.

O que vem a seguir

Você agora conhece o vocabulário padrão. A questão restante de ergonomia de lambdas é "quando o corpo da lambda apenas delega a um método existente, posso escrever de forma mais curta?" Sim — com referências a métodos. O próximo capítulo, Referências a Métodos em Java, cobre o operador :: e suas quatro formas (estático, instância ligada, instância não ligada, construtor), e explica quando uma referência a método é mais clara que uma lambda e quando não é.

Prática

Prática
Um método declara `void each(Consumer<String> action)`. Quais dos seguintes são argumentos válidos?
Um método declara `void each(Consumer<String> action)`. Quais dos seguintes são argumentos válidos?
Was this page helpful?