HOWTO · Java
O operador de seta -> em Java
Entenda o significado da sintaxe `->` em Java e veja como usar expressões lambda com interfaces funcionais, coleções, streams e threads.
Nesta página
Em Java, -> separa os parâmetros de uma expressão lambda, à esquerda, do corpo, à direita; não é um operador de uso geral para cálculos. Uma lambda fornece uma implementação para uma interface funcional compatível, isto é, uma interface com um único método abstrato. Introduzidas no Java 8, as lambdas podem encurtar algumas implementações, mas não substituem todas as classes anônimas nem garantem código mais rápido ou mais claro.
O significado do operador de seta em Java
Antes do Java 8, uma tarefa para Runnable podia ser escrita como uma classe anônima com um método run. Uma lambda expressa o mesmo comportamento como () -> System.out.println("Working"). A seta continua fazendo parte de uma expressão lambda: ela não cria um valor de função independente de um tipo de destino. O compilador determina a interface funcional pelo contexto, como uma declaração de variável ou um argumento de método.
Sintaxe e regras das expressões lambda
Uma lambda tem uma lista de parâmetros, a seta e um corpo. Quando o corpo é uma expressão, as chaves podem ser omitidas, como em x -> x * x; um corpo em bloco usa chaves e precisa de um return explícito quando o método de destino retorna um valor. Uma lambda sem parâmetros usa (), e vários parâmetros são escritos entre parênteses, como em (left, right) -> left + right. Também é válido colocar parênteses em torno de um único parâmetro, como em (name) -> name.length().
Em uma lambda com corpo em bloco que retorna um valor, o tipo de retorno do método de destino determina o tipo do resultado. Neste exemplo, a lambda é atribuída a ToIntFunction<String>, cujo método applyAsInt retorna um int:
O código e os rótulos ou saídas exibidos nesta seção permanecem em inglês.
import java.util.function.ToIntFunction;
public class BlockLambdaExample {
public static void main(String[] args) {
ToIntFunction<String> length = (String name) -> {
return name.length();
};
System.out.println(length.applyAsInt("Mira"));
}
}
O corpo precisa respeitar o tipo de retorno do método de destino e as regras para exceções verificadas. Uma lambda não pode ser atribuída a uma classe arbitrária nem a uma interface com vários métodos abstratos não relacionados. Para essa regra, métodos default e estáticos da interface não contam como métodos abstratos adicionais.
Uso de lambdas com interfaces funcionais
Uma lambda pode ser atribuída a uma interface funcional simples e invocada por meio do método dessa interface. Este exemplo autocontido coloca primeiro a classe com main, para que possa ser executado diretamente. A saída é a linha impressa por draw(); a mensagem exibida no exemplo permanece em inglês.
Execute este exemplo:
public class ArrowExample {
@FunctionalInterface
interface Drawable {
void draw();
}
public static void main(String[] args) {
Drawable shape = () -> System.out.println("Drawing a circle");
shape.draw();
}
}
Drawing a circle
Usos comuns em coleções, streams e threads Java
Métodos de coleções, como forEach, aceitam uma interface funcional; assim, uma lambda pode indicar o que fazer com cada elemento. Neste exemplo, name representa o elemento atual da lista, e a lambda Consumer<String> o imprime:
O código e os rótulos ou saídas exibidos nesta seção permanecem em inglês.
import java.util.Arrays;
import java.util.List;
public class CollectionLambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Ava", "Mira");
names.forEach(name -> System.out.println(name));
}
}
O método filter de um stream recebe uma lambda Predicate<String> para manter os elementos que atendem ao critério. A operação forEach a seguir imprime cada nome que passa pelo predicado:
import java.util.Arrays;
import java.util.List;
public class StreamLambdaExample {
public static void main(String[] args) {
List<String> names = Arrays.asList("Ava", "Mira", "Bo");
names.stream()
.filter(name -> name.length() > 2)
.forEach(name -> System.out.println(name));
}
}
Essas APIs oferecem maneiras diferentes de expressar a iteração e o processamento, mas um laço tradicional pode ser mais adequado quando facilita acompanhar o fluxo de controle.
Runnable é usado com frequência para representar a tarefa de uma thread, pois seu único método abstrato não recebe argumentos nem retorna um valor. Esta lambda fornece a tarefa Runnable; construir a Thread não a executa, enquanto start() agenda sua execução independente. A mensagem impressa pela tarefa permanece em inglês:
public class ThreadLambdaExample {
public static void main(String[] args) {
Runnable task = () -> System.out.println("Task running");
Thread worker = new Thread(task);
worker.start();
}
}
A saída da thread pode aparecer em uma ordem diferente em relação a outras tarefas concorrentes, e o agendamento não é garantido. Use lambdas quando a interface de destino e o código ao redor deixarem o comportamento claro; elas reduzem a quantidade de sintaxe, mas não eliminam a necessidade de entender a API ou o comportamento da concorrência.