3. Expressões lambda
• Uma expressão lambda é uma função anônima
• É abstração de uma operação, tratada como se fosse dados,
permitindo que seja atribuída a variáveis e retornada/passada
de/para métodos
• Tem origem no cálculo lambda (Alonzo Church, 1936) e é
usado em programação desde Lisp (1958)
• Faz parte de várias linguagens populares: JavaScript, Scala, C#,
Go, Swift, Smalltalk, Python
3
4. Programação funcional
• Lambdas são uma abstração fundamental em linguagens
funcionais
• Em orientação a objetos, o estado de objetos é modificado por
operações representadas por seus métodos
• Na programação funcional, operações são representadas por
funções entre objetos imutáveis
• Embora não tenha sido concebida como linguagem funcional, é
possível programar em Java usando o paradigma funcional
• AAPI de streams facilita a programação funcional em Java 8
4
5. Métodos anônimos em Java
• Desde Java 1.1 pode-se implementar interfaces anônimas
Runnable tarefa = new Runnable() {
@Override
public void run() {
System.out.println("Hello!");
}
}
• Java 8 vai mais longe com métodos anônimos (lambdas)
Runnable tarefa = () -> { System.out.println("Hello!"); }
• As chaves são opcionais neste caso:
Runnable tarefa = () -> System.out.println("Hello!");
5
6. Sintaxe de expressões lambda
• Novo operador -> (seta) separa parâmetros e expressão
• Variáveis usadas no bloco da expressão lambda devem ser efetivamente
finais (imutáveis)
• Parenteses: obrigatórios se há mais de um parâmetro ou nenhum:
• p -> p/2; // mesmo que (p) -> p/2
• () -> 3; // parênteses necessários
• (a,b) -> a+b; // parênteses necessários
• Declarar tipo dos parâmetros é opcional (se for possível inferir)
• int r = p -> p/2; // Implícito: (int p) -> p/2;
• Chaves e a instrução return: opcionais se apenas uma instrução
int r = (a,b) -> a+b; // (int a, int b) -> { return a+b;}
6
7. Referências de métodos
• O operador :: declara referências para métodos e construtores
• Há quatro tipos
• Classe::metodoEstatico // referência para metodo estatico
• Classe::metodoDeInstancia // ref. para método de um objeto qualquer
• objeto::metodoDeInstancia // ref. para método de um objeto específico
• Classe::new // referência para construtor
• Exemplos
• System.out::println Comparator::compare
HashSet<Integer>::new
• Uma referência pode substituir uma expressão lambda; os argumentos são
obtidos por inferência (as duas formas abaixo são equivalentes)
Consumer<String> f1 = s -> System.out.println(s);
Consumer<String> f2 = System.out::println;
7
interface Consumer<T> {
void accept(T t);
}
Usará parâmetro T do
método por inferência
8. Interfaces funcionais
• Interface funcional: interface com um método abstrato
• Se houver outros métodos devem ser static ou default
• Podem ser implementadas usando expressões lambda!
• Java 8 introduziu a anotação @FunctionalInterface para identificar
interfaces funcionais em tempo de compilação
• Haverá erro de compilação se interface não for funcional
• O uso de interfaces funcionais em lambdas permite alto grau de
reuso: lambdas ignoram tipos recebidos/retornados
• O pacote java.util.function contém uma coleção de interfaces padrão
para reuso
8
9. java.util.function
• 43 interfaces genéricas de propósito geral
• Principais interfaces
• Predicate<T>: Recebe (T); Retorna boolean
• BiPredicate<T,U>: Recebe (T,U); Retorna boolean
• Consumer<T>: Recebe (T); Retorna void
• Supplier<T>: Recebe (); Retorna T
• Function<T,R>: Recebe (T); Retorna R
• BiFunction<T,U,R>: Recebe (T,U); Retorna R
• UnaryOperator<T>: Recebe (T); Retorna T
• BinaryOperator<T, T>: Recebe (T, T); Retorna T
9
10. Uso de interfaces funcionais
• O nome do método da interface funcional é irrelevante para criar
expressões lambda, já que é anônimo; tipos também são irrelevantes
• O mais importante são: quantidade de argumentos recebidos e
se retorna ou não algum valor
• Uma expressão usando Supplier<T> com T = String
• Uma expressão usando Function<Integer,String>
• Uma expressão usando BiFunction<T, U, R>
10
String produto = () -> "Hello!";
String resultado = (a,b) -> a + b; // concatena ou soma
Integer tamanho = s -> s.length(); // recebe String, retorna Integer
11. Métodos que recebem lambdas
• Um método que recebe uma expressão lambda declara
receber uma interface funcional
• Pode-se implementar uma função anônima e passá-la como
argumento, da mesma forma como são passados os dados
11
public Integer calcular(Function<Integer> funcao,
Integer operando) {
return funcao.apply(operando);
}
System.out.println( calcular( n -> n * n, 4);
System.out.println( calcular( n -> n / 2, 16);
12. Streams
• A classe java.util.stream.Stream fornece uma API para
programação funcional baseado na concatenação de operações
lazy processadas quando uma operação terminal é chamada
• Um Stream conduz elementos de uma fonte através de uma pipeline
de operações, produzindo um resultado sem modificar a fonte
• Elementos são processados uma única vez (o stream é consumido)
• Streams podem ser infinitos (operações intermediárias podem limitar
os dados durante o processamento)
• Streams podem ser criados/obtidos de várias formas
12
13. Como criar um Stream
• Pode ser criado a partir de métodos de fábrica: generate(), iterate(), of(), etc.
Stream<String> letras = Stream.of("X", "T", "S", "P");
Stream<Integer> infinito =
Stream.generate(()-> new Random().nextInt(100));
• É mais comum criar um stream a partir de uma coleção usando o método
stream() ou parallelStream()
List<Integer> colecao = new ArrayList(); // ...
Stream<Integer> colecao.stream();
Stream<Integer> colecao.parallelStream(); // paralelismo
• Um stream pode ser transformado via operações intermediárias (lazy) e
uma operação terminal (eager - que encerra o stream)
13
14. Operações de um stream
• As operações intermediárias (lazy) são executadas apenas depois que o
stream é terminado (puxando os dados: método "pull")
• Após uma operação terminal o stream não pode ser reusado
• As operações recebem interfaces funcionais
• Algumas operações. Operações intermediárias retornam Stream:
• filter(Predicate<T>): intermediária
• map(Function<T,U>): intermediária
• flatMap(Function<T,Stream<R>>): intermediária
• reduce(BinaryOperator<T>): terminal
• forEach(Consumer<T>): terminal
• collect(Collector<T,A,R>): terminal
14
Outras operações terminais:
min(), max(), count(), etc.
Outras intermediárias:
skip(), peek(), distinct(), etc.
15. Filter
• A operação intermediária filter(Predicate<T>) remove do
stream elementos que não combinam com a função
15
List<Integer> numbers =
Arrays.asList(new Integer[] {4,1,9,6,8,3,5});
numbers.stream()
.filter(n -> n > 5)
.forEach(System.out::println);
Imprime 9
6
8
16. Map
• A operação intermediária map(Function<T,U>) recebe
uma função que realiza uma transformação no stream (e
pode converter um tipo em outro):
16
List<Integer> numbers =
Arrays.asList(new Integer[] {4,1,9,6,8,3,5});
numbers.stream()
.filter(n -> n > 5)
.map(n -> n * n)
.forEach(System.out::println);
Imprime 81
36
64
17. ForEach
• O método forEach(Consumer<T>) foi adicionado em Iterable
e está disponível para qualquer implementação (ex: Collection):
list.forEach(System.out::println);
• Stream também implementa Iterable e pode chamar forEach():
Stream<Integer> numeros = Stream.of(1,2,3,4);
numeros.map(s->s*2).forEach(System.out::println);
• ForEach é uma operação terminal (depois de chamada, puxa a
execução do stream não permitindo novos métodos):
numeros.filter(n->n<3); // exceção (stream encerrado)
17
18. Reduce
• reduce(BinaryOperator<T>) é uma operação terminal que retorna
resultado da operação de combinação sobre valores acumulados
• Há três diferentes versões de reduce (com 1, 2 ou 3 args)
• O resultado pode ser do mesmo tipo (T), outro tipo ou
Optional<T> (objeto que encapsula T ou null)
18
List<Integer> numbers =
Arrays.asList(new Integer[] {4,1,9,6,8,3,5});
Optional<Integer> resultado = numbers.stream()
.filter(n -> n > 5)
.map(n -> n * n)
.reduce((acum, oper) -> a+b);
int soma = resultado.get(); // 181 (81+36+64)
(veja também as outras duas formas de implementar reduce())
faz o mesmo que sum()
19. Collect
• collect(Collector<T,A,R>) é uma operação terminal que executa
uma redução mutável nos elementos do stream. O coletor constrói
o resultado usando funções de acumulação e combinação.
• collect() pode ser usado para reduzir um stream a uma coleção
• Classe utilitária Collectors contém vários algoritmos
implementados (toList(), groupingBy(), etc.)
19
Map<String, List<Movie>> directors = movieList.stream()
.collect(Collectors.groupingBy(Movie::getDirector));
List<String> titles = movieList
.stream().map(movie -> movie.getTitle()
+ " (" + movie.getDirector()+ ")")
.collect(Collectors.toList());
20. FlatMap
• flatMap(Function<T,Stream<R>>) é uma operação
intermediária que "achata" um stream de streams
20
List<String> titlesAndDirectors =
movieList.stream()
.flatMap(movie ->
Stream.of(movie.getTitle(),
movie.getDirector()))
.collect(Collectors.toList());
21. Exceções e valores nulos
• Streams não podem deixar escapar exceções checadas
• É preciso capturar a exceção
• Pode-se lançar uma exceção de runtime
• Ideal é devolver um objeto comum que contenha informações que
permitam lidar com o problema
• Objetos Optional podem ser usados para lidar com valores nulos
21
public Optional<Integer> transformar(Integer valor) {
try {
// operações sobre valor
return Optional.of(resultado);
} catch (Excecao e) {
return Optional.empty();
}
} int valor = 123;
Optional<Integer> op = transformar(valor);
Integer resultado = op.orElse(valor);
Retorna resultado ou
123 (se ocorrer exceção
e Optional estiver vazio)
22. Streams de primitivos
• java.util.stream também contém um conjunto de Streams de primitivos
como DoubleStream, IntStream e LongStream
• Streams comuns podem ser convertidos em streams de primitivos:
mapToInt(), mapToDouble(), etc.
IntStream stream = lista.stream().mapToInt(n->n);
• Possuem métodos especiais para manipular de primitivos e obter estatísticas
22
IntSummaryStatistics example =
Stream.of(9,4,8,2,15,82,91,77,53,27,13)
.mapToInt(n->n).summaryStatistics();
System.out.printf("Count: %d, Max: %d, Min: %d, Avg: %f, Sum: %d",
example.getCount(), example.getMax(), example.getMin(),
example.getAverage(), example.getSum());
23. Exercícios
• 1. A classe Movies possui uma lista de objetos Movie que pode ser
obtida com o método getMovies(). Escreva código usando streams que:
• Conte a quantidade de filmes existentes
• Conte a quantidade de filmes de "Stanley Kubrick"
• Obtenha uma lista de filmes com duração menor que 100 minutos
• Obtenha um mapa contendo diretores (String) e uma lista de seus
filmes (Movie)
• Descubra qual o filme mais longo, e o mais curto
• Coloque os filmes em ordem cronológica
• 2. Refatore os exercícios do capítulo 9 para usar Streams (use streams,
lambda e referências de métodos) onde for possível.
• 3. Implemente a função min() usando reduce()
• 4. Transforme a lista de filmes em uma tabela HTML (String)
23
24. JAVA 8
Helder da Rocha
helder@argonavis.com.br
para programadores
02/02/2015