W3docs

Interface Function do Java

Transforme um valor de um tipo em outro no Java com a interface Function e os métodos andThen/compose.

Function<T, R> é a interface funcional para a pergunta "transforme este T em um R" — uma entrada, uma saída, sem efeitos colaterais esperados. É a forma que Stream.map aceita, a forma que Optional.map aceita, a forma que Map.computeIfAbsent aceita, e a forma que todo método do JDK que diz "transforme isto em aquilo" recebe. Um único método abstrato, três ou quatro métodos padrão úteis, e uma pequena álgebra (andThen, compose, identity) para encadear transformações sem escrever lambdas intermediárias.

A interface

@FunctionalInterface
public interface Function<T, R> {
  R apply(T t);                                                    // the only abstract method

  default <V> Function<V, R> compose(Function<? super V, ? extends T> before);
  default <V> Function<T, V> andThen(Function<? super R, ? extends V> after);
  static  <T> Function<T, T> identity();
}

apply(T) é o SAM (single abstract method). Todo lambda ou referência de método que termina em uma posição Function<T, R> o implementa.

Function<String, Integer> length = String::length;
int n = length.apply("hello");                  // 5

Normalmente você deixará stream.map(length) ou optional.map(length) chamar apply por você. Conhecer o nome do método importa quando você escreve código que aceita um Function<T, R> e precisa chamá-lo uma vez.

andThen e compose — duas formas de encadear

Os dois métodos padrão constroem um novo Function encadeando o receptor com outro. Eles diferem apenas na direção:

Function<String, String>  trim     = String::trim;
Function<String, Integer> length   = String::length;

Function<String, Integer> trimThenLength = trim.andThen(length);     // f.andThen(g): g(f(x))
Function<String, Integer> sameThing      = length.compose(trim);     // g.compose(f): g(f(x))

Ambos constroem o mesmo pipeline s -> length(trim(s)). A diferença é qual deles melhor no ponto de chamada:

  • andThenda esquerda para a direita, na mesma ordem em que os dados fluem. trim.andThen(length).andThen(asString) é "trim, depois length, depois asString."
  • composeda direita para a esquerda, da forma como a composição matemática é escrita: f ∘ g significa "aplique g primeiro, depois f." length.compose(trim) é "length após trim."

No código de aplicação, andThen é quase sempre a escolha mais clara — o código lê de cima para baixo, da esquerda para a direita, e um pipeline da esquerda para a direita combina com isso. compose é útil quando você tem uma função final e quer antepor pré-processamento sem reescrever a cadeia.

Ambos são preguiçosos no sentido de que não executam nada no momento da composição; eles simplesmente produzem um novo Function cujo apply chama os subjacentes na ordem correta.

Function.identity() — a transformação que não faz nada

Function<T, T> id = Function.identity();      // t -> t

identity() retorna a mesma instância a cada chamada (um lambda singleton), portanto tem custo zero de alocação. O único lugar onde ele se destaca é como mapeador de chave ou valor em Collectors.toMap, onde você precisa passar um Function mesmo quando o valor é "o próprio elemento":

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

Sem Function.identity() você escreveria p -> p, que aloca um novo lambda a cada chamada e lê pior.

Um ponto sutil: identity() só funciona quando os tipos de entrada e saída são os mesmos. No momento em que um genérico se amplia (Function<? super T, ? extends R>), o compilador pode forçar você a escrever um lambda novamente. É um caso extremo, mas vale saber quando a inferência de tipos reclama.

Function<T, R> versus UnaryOperator<T>

UnaryOperator<T> é a especialização para o caso em que entrada e saída são do mesmo tipo:

UnaryOperator<String> upper = String::toUpperCase;       // String -> String
Function<String, String> sameShape = String::toUpperCase;

Ambas são instâncias válidas de Function<String, String>UnaryOperator<T> estende Function<T, T>. A diferença está no nível da API: List.replaceAll, Map.replaceAll e Comparator.thenComparing(UnaryOperator) declaram UnaryOperator<T> porque "substituir cada elemento por um valor transformado do mesmo tipo" é exatamente essa forma. Passe uma referência de método e o compilador escolhe a correta.

BiFunction<T, U, R> — duas entradas

A forma com dois argumentos:

BiFunction<String, Integer, String> repeat = String::repeat;
String s = repeat.apply("ab", 3);             // "ababab"

BiFunction tem o mesmo andThen, mas não tem compose — a assimetria é proposital, porque pré-processar uma função de dois argumentos precisaria de dois parâmetros compose.

O JDK usa BiFunction<K, V, V> para Map.merge e BiFunction<K, V, V_NEW> para Map.compute. BinaryOperator<T> é o caso especial em que todos os três parâmetros de tipo são T (entrada, entrada e saída iguais) — coberto no capítulo sobre BinaryOperator.

Especializações primitivas — três famílias

Function<Integer, String> encaixa o int em um objeto a cada chamada. O pacote traz três famílias para evitar isso:

// 1. Primitive in, object out — "IntFunction<R>"
IntFunction<String>     fromInt   = i -> "n=" + i;

// 2. Object in, primitive out — "ToIntFunction<T>"
ToIntFunction<String>   strLen    = String::length;
ToDoubleFunction<Item>  price     = Item::price;

// 3. Primitive in, primitive out — "IntToLongFunction", "IntUnaryOperator", etc.
IntToLongFunction       square    = i -> (long) i * i;
IntUnaryOperator        doubleIt  = i -> i * 2;
DoubleUnaryOperator     halve     = d -> d / 2.0;

A nomenclatura lê como uma frase:

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

Stream.mapToInt(ToIntFunction) é a ponte de um Stream<T> com boxing para um IntStream. Uma vez em um IntStream, toda transformação usa IntUnaryOperator ou IntToLongFunction — e o custo do boxing permanece em zero.

Um exemplo prático: composição, identity e uma especialização primitiva

O programa abaixo constrói dois Functions, os compõe com andThen e compose para mostrar que são equivalentes, usa Function.identity() dentro de Collectors.toMap, e contrasta um Function<Integer, Integer> com boxing com um IntUnaryOperator primitivo em uma carga de trabalho grande o suficiente para sentir o custo do boxing.

java— editable, runs on the server

O que observar na execução:

  • trim.andThen(upper) e upper.compose(trim) produziram a mesma String a partir da mesma entrada. Diferem apenas em qual nome lê naturalmente onde você o escreve — andThen combina com o fluxo de dados da esquerda para a direita, compose combina com a notação matemática "f após g".
  • A cadeia mais longa trim.andThen(upper).andThen(length) mudou o tipo de saída de String para Integer ao longo do caminho. O pipeline compõe com segurança de tipos; o compilador rastreou String -> String -> String -> Integer por você.
  • Function.identity() encaixou em Collectors.toMap(Person::name, Function.identity()) como o mapeador de valor. O lambda p -> p teria funcionado, mas identity() é a forma singleton sem alocação e lê como a intenção ("o valor é a pessoa").
  • O Function<Integer, Integer> com boxing paga por dois boxings de Integer a cada chamada; o IntUnaryOperator primitivo não paga nada. Uma única execução aquecida pode mostrar tempos semelhantes — o JIT é bom em eliminar boxes de vida curta — mas sob pressão de alocação real (heaps grandes, GC concorrente, valores escapando) a variante primitiva é a que se mantém. Recorra a ela em pipelines quentes que processam milhões de valores.
  • BiFunction.andThen(Function) encadeou uma função de dois argumentos com um acompanhamento de um argumento. Não há BiFunction.compose — pré-processar duas entradas precisaria de dois argumentos compose, o que a API deliberadamente evita.

O que vem a seguir

Function<T, R> e Predicate<T> são ambas formas puras — entrada, saída, sem efeitos colaterais esperados. O próximo capítulo, Java Consumer e Supplier, cobre as duas interfaces que saem dessa pureza: Consumer<T> recebe uma entrada e não produz nada (um efeito colateral — imprimir, registrar, armazenar), e Supplier<T> não recebe nada e produz uma saída (padrão preguiçoso, fábrica, aleatoriedade). Elas completam a taxonomia dos quatro cantos que você viu na visão geral das interfaces integradas.

Prática

Prática
Você tem `Function<String, String> trim = String::trim;` e `Function<String, Integer> length = String::length;`. Você quer um `Function<String, Integer>` que trima primeiro e depois mede o comprimento. Qual expressão constrói isso mais naturalmente?
Você tem `Function<String, String> trim = String::trim;` e `Function<String, Integer> length = String::length;`. Você quer um `Function<String, Integer>` que trima primeiro e depois mede o comprimento. Qual expressão constrói isso mais naturalmente?
Was this page helpful?