12Programação Funcional Helder da Rocha helder@argonavis.com.br
Conteúdo 1.Introdução 2.Sintaxe 3.Expressões 4.Arrays e Strings 5.Classes 6.Interfaces 7.Exceções 2 8.Objetos 9.Coleções 10.Datas 11.Threads 12.Programação funcional 13.Arquivos e I/O 14.Bancos de dados
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
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
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
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
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
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
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
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
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);
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
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
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.
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
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
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
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()
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());
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());
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)
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());
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
JAVA 8 Helder da Rocha helder@argonavis.com.br para programadores 02/02/2015

Curso de Java: Introdução a lambda e Streams

  • 1.
    12Programação Funcional Helder daRocha helder@argonavis.com.br
  • 2.
  • 3.
    Expressões lambda • Umaexpressã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 • Lambdassã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 emJava • 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õeslambda • 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 • Interfacefuncional: 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 interfacesgené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 interfacesfuncionais • 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 recebemlambdas • 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 classejava.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 umStream • 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 umstream • 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çãointermediá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çãointermediá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étodoforEach(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 valoresnulos • 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. Aclasse 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 daRocha helder@argonavis.com.br para programadores 02/02/2015