W3docs

Referências de Método em Java

Use referências de método em Java com o operador :: — estáticas, de instância, vinculadas e de construtor.

Uma referência de método é uma sintaxe abreviada para uma lambda cujo corpo não faz nada além de chamar um método existente. Quando uma lambda é literalmente x -> SomeClass.foo(x) ou (a, b) -> a.bar(b), o operador :: permite escrever como SomeClass::foo ou Some::bar. O compilador produz o mesmo valor das duas formas — uma instância da interface funcional adequada — portanto, onde uma lambda se encaixa, uma referência de método compatível também se encaixa.

Function<String, Integer> len1 = s -> s.length();
Function<String, Integer> len2 = String::length;            // identical at runtime

List<String> names = List.of("Bob", "Alice");
names.forEach(s -> System.out.println(s));                   // lambda
names.forEach(System.out::println);                          // method reference

As quatro formas abaixo cobrem todas as referências de método que você escreverá. A única habilidade necessária é reconhecer qual forma se aplica a um determinado ponto de chamada.

Forma 1: Referência a método estático — ClassName::staticMethod

O método é um método static em uma classe. A referência se torna uma lambda cujos parâmetros são os parâmetros do método estático:

Function<String, Integer> parse  = Integer::parseInt;        // s -> Integer.parseInt(s)
BinaryOperator<Integer>   max    = Math::max;                 // (a, b) -> Math.max(a, b)
Function<Object, String>  toStr  = String::valueOf;            // o -> String.valueOf(o)

Esta é a forma que aparece em código de stream como nums.stream().reduce(0, Integer::sum)Integer.sum(int, int) é estático, então Integer::sum é um BinaryOperator<Integer> (um BiFunction<Integer, Integer, Integer>).

Forma 2: Referência a método de instância vinculada — instance::method

O método é um método de instância em um objeto específico e nomeado. A referência se torna uma lambda cujos parâmetros são os parâmetros do método (a instância é capturada):

PrintStream out = System.out;
Consumer<String> print = out::println;                  // s -> out.println(s)

String prefix = "Hello, ";
Function<String, String> greet = prefix::concat;        // name -> prefix.concat(name)

List<String> log = new ArrayList<>();
Consumer<String> record = log::add;                     // msg -> log.add(msg)

O receptor vinculado preenche o slot sem argumento: como prefix já está capturado, greet só precisa do argumento para concat, tornando-se uma Function<String, String> em vez de uma BiFunction. Da mesma forma, log::add mantém log fixo e expõe apenas o elemento a ser adicionado, resultando em um Consumer<String>.

A instance capturada é mantida pelo objeto resultante, de forma semelhante a como uma lambda captura variáveis locais effectively final. Referências vinculadas são a maneira de dizer "use o método deste objeto como callback" — Logger::info em um logger específico, event::handle em um manipulador específico.

Forma 3: Referência a método de instância não vinculada — ClassName::method

O método é um método de instância, mas você o referencia por meio da classe em vez de uma instância específica. A referência se torna uma lambda cujo primeiro parâmetro é o receptor, e o restante são os próprios parâmetros do método:

Function<String, Integer>   len    = String::length;             // s -> s.length()        — first param is the receiver
Function<String, String>    upper  = String::toUpperCase;         // s -> s.toUpperCase()
BiPredicate<String, String> starts = String::startsWith;          // (s, prefix) -> s.startsWith(prefix)

Esta é a forma que confunde as pessoas. String::length parece que poderia significar "o método length na classe String" — mas não existe tal método estático. Na verdade, significa "dado qualquer String, chame seu método de instância length() — o receptor é o primeiro parâmetro da lambda." É por isso que String::length é uma Function<String, Integer> (uma entrada, uma saída) e String::startsWith é uma BiPredicate<String, String> (a segunda entrada é o prefixo que o receptor testa).

Esta forma é o motor por trás de quase todo pipeline de stream:

people.stream()
    .map(Person::name)               // unbound: p -> p.name()
    .filter(s -> s.startsWith("A"))
    .map(String::toUpperCase)        // unbound: s -> s.toUpperCase()
    .forEach(System.out::println);   // bound: s -> out.println(s)

Forma 4: Referência a construtor — ClassName::new

Referencia um construtor como uma função. A lambda resultante recebe os parâmetros do construtor e retorna uma nova instância:

Supplier<List<String>>           listOf  = ArrayList::new;                  // () -> new ArrayList<>()
Function<Integer, ArrayList<?>>  sized   = ArrayList::new;                   // n -> new ArrayList<>(n)
Function<String, BigDecimal>     toBig   = BigDecimal::new;                  // s -> new BigDecimal(s)
BiFunction<String, Integer, AbstractMap.SimpleEntry<String, Integer>> entry =
    AbstractMap.SimpleEntry::new;

Referências a construtores são a forma como Collectors.toCollection(TreeSet::new) permite escolher um tipo de destino, e como Stream.generate(Random::new) produz objetos Random independentes a cada chamada de get().

Arrays têm uma forma especial: String[]::new é uma IntFunction<String[]>n -> new String[n]. É isso que stream.toArray(String[]::new) utiliza.

Referência de método vs lambda — quando cada uma é melhor

Uma referência de método é a escolha certa quando o corpo da lambda é exatamente uma única chamada de método com os parâmetros passados na ordem:

LambdaReferência de método
s -> s.length()String::length
s -> System.out.println(s)System.out::println
(a, b) -> a.compareTo(b)String::compareTo
() -> new ArrayList<>()ArrayList::new

Uma lambda é a escolha certa quando o corpo faz qualquer outra coisa:

  • Chama mais de um método: s -> s.trim().toUpperCase() (sem referência para a cadeia).
  • Tem qualquer transformação de argumento: s -> System.out.println("[" + s + "]").
  • Tem qualquer fluxo de controle: n -> n < 0 ? 0 : n.
  • Reordena ou duplica argumentos: (a, b) -> b.compareTo(a) (comparador invertido).

A otimização não é sobre velocidade em tempo de execução — ambas compilam para o mesmo bootstrap invokedynamic. É sobre legibilidade. Person::name salta aos olhos como "o campo name," enquanto p -> p.name() exige a leitura de três tokens. Quando a referência se encaixa, prefira-a; quando não, não torça o código para forçá-la.

Um problema com referências a construtor: sobrecargas ambíguas

ClassName::new funciona bem quando há um construtor correspondendo à interface alvo. Quando há vários, o compilador escolhe com base na contagem e nos tipos de parâmetros do tipo alvo. Na maioria das vezes isso funciona; ocasionalmente não funciona, e você precisa desambiguar tipando a variável explicitamente ou voltando a uma lambda:

// ArrayList has constructors: (), (int), (Collection)
Supplier<ArrayList<String>>           a = ArrayList::new;     // picks the no-arg
Function<Integer, ArrayList<String>>  b = ArrayList::new;     // picks the (int) one
Function<List<String>, ArrayList<String>> c = ArrayList::new; // picks the (Collection) one

// var inference can't disambiguate — this would not compile:
// var ambiguous = ArrayList::new;

A solução é manter o tipo alvo explícito, como em a, b, c acima.

Um exemplo completo: as quatro formas em um único programa

O programa abaixo constrói e usa uma referência de método de cada forma, demonstra como String::length (não vinculada) se torna uma Function<String, Integer> e mostra o truque da referência a construtor que impulsiona stream().toArray(T[]::new).

java— editable, runs on the server

O que observar na execução:

  • Todas as quatro formas compilam para instâncias de interfaces funcionais comuns — parse é uma Function<String, Integer> tanto se você escreveu s -> Integer.parseInt(s) quanto Integer::parseInt. A abreviação é puramente sintática.
  • As formas não vinculadas String::length e String::toUpperCase ambas têm um receptor como primeiro parâmetro. É por isso que String::length é uma Function<String, Integer> e String::startsWith é uma BiPredicate<String, String> — o receptor é um slot, e o parâmetro explícito é o outro.
  • A referência a construtor String[]::new produziu uma IntFunction<String[]> — a forma que stream().toArray(...) espera. Referências a construtor são a maneira de dizer a um stream "aqui está o tipo de destino."
  • O comparador de tamanho invertido não poderia ser escrito como uma referência de método: o receptor e o parâmetro se invertem, e referências de método não podem reordenar argumentos. Esse é exatamente o tipo de caso em que uma lambda ainda é a escolha certa.

O que vem a seguir

Agora você pode escrever um pipeline de stream quase inteiramente em referências de método e deixar as poucas transformações que genuinamente precisam de formatação viverem em pequenas lambdas. Esse estilo é a introdução natural à peça central desta parte: os streams. O próximo capítulo, Introdução ao Java Streams, apresenta a API Stream<T> — o que é, como é um pipeline de stream, por que é lazy, por que só pode ser usado uma vez e como se encaixa com as lambdas, interfaces funcionais e referências de método que você acabou de aprender.

Prática

Prática
`String::length` é qual tipo de referência de método e qual interface funcional ela corresponde?
`String::length` é qual tipo de referência de método e qual interface funcional ela corresponde?
Was this page helpful?