Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
September 13, 2022 05:05 pm GMT

[PT-BR] Transformando listas com Java Stream API

E a pessoal! Espero que todos estejam bem!!!

Bom, aps descansar do TDC Business 2022, onde tive a honra e a felicidade de realizar um sonho: ser coordenador na trilha de Carreira e Mentoria, ser expositor da empresa em que trabalho (StackSpot) e ser palestrante na trilha de arquitetura Java, falando sobre Streams API junto com Rolmer Telis de Oliveria, estou de volta explorando, estudando e aprendendo cada vez mais sobre Programao Funcional com Java.

No artigo anterior, vimos que a partir do Java 8, a interface Iterable foi melhorada com um mtodo especial: um default method chamado forEach. Agora, caso estivermos trabalhando com objetos que implementam a interface Iterable, poderemos usufruir desse mtodo para iterar nos itens de maneira declarativa, utilizando uma lambda expression ou method references.

Mas o que so Default Methods

A feature default method, que foi adicionado ao Java 8, permitiu uma evoluo suave de toda a API do Java, permitindo mtodos padres fossem implementados em interfaces, mantendo assim toda uma retro-compatibilidade das verses anteriores do Java.

Com isso, podemos dizer que todos objetos que implementam a interface Iterable podero usufruir do mtodo forEach, j que esse um default method, implementado na prpria interface Iterable.

Muito mais que iterar em uma lista

J conhecemos o mtodo forEach, na qual podemos iterar nos itens de uma determinada lista ou colees, mas sabemos tambm que s iterar no nos basta!

Sem dvida, vamos ter que realizar operaes muito mais complexas: como filtrar, transformar ou modificar os dados dessas listas.

Manipular colees para produzir outros resultados to comum como iterar atravs dos elementos de uma coleo.

Vamos dizer que precisamos pegar uma lista de produtos e pegar s os preos desses produtos.

Abaixo segue o Record que representar nosso produto:

PS: Vamos utilizar a especificao JSR 354 Java Money1

  record Product(String description, MonetaryAmount price) {}

E aqui est a lista de produtos:

  var currency = Monetary.getCurrency("BRL");  var products = List.of(      new Product("bean", Money.of(5.99,currency)),      new Product("rice", Money.of(12.49,currency)),      new Product("coffee", Money.of(18.99,currency)),      new Product("bread", Money.of(6.59,currency)),      new Product("chocolate", Money.of(6.80,currency))  );

Uma vez que estamos utilizando uma lista imutvel criada atravs do mtodo List.of, ns precisamos criar uma nova lista para armazenar esses preos.

Utilizando uma abordagem imperativa, ns escreveramos um cdigo parecido com esse:

  List<MonetaryAmount> prices = new ArrayList<>();  for(Product product: products){      prices.add(product.price());  }

Precisamos criar uma lista vazia para s ento coletar todos os preos ao iterar pelos produtos da lista. Como primeiro passo para utilizar um estilo funcional, vamos usar o internal interator forEach fornecido pela interface Iterable, como fizemos no blogpost anterior.

  List<MonetaryAmount> prices = new ArrayList<>();  products.forEach(product -> prices.add(product.price()));

Estamos utilizando um internal iterator, mas ainda h a necessidade de criar uma lista vazia e ainda assim adicionar os preos item a item. Que tal explorar um pouco uma abordagem mais funcional para solucionar esse detalhe?

Muito mais que um simples iterador!

A criao de uma lista vazia e a manipulao explicita desta instncia introduz variveis mutveis, o que deixam o nosso cdigo mais propenso a erros, pois estamos, alm de iterar pelos produtos, temos que nos preocupar em coletar s os preos e adicion-los na nova lista: estamos instruindo nosso programa como a coleta deve ser feita ao invs de declarar qual a informao que queremos coletar explicitamente.

A partir do Java 8, a interface Stream2 foi adicionada com o intuito de permitir a manipulao de colees de maneira mais declarativa, mais funcional.

A interface Stream muito mais que um simples iterador: ela fornece uma API fluente na qual nos permite, em conjunto com Lambda Expressions e Reference Methods, encadear operaes que podem ou no utilizar objetos de funo afim de nos permitir executar tarefas como filtrar, mapear ou transformar, contar, reduzir e por a vai.

Graas aos default methods, objetos que implementam uma Collection detm o mtodo stream para adquirir instncias do tipo Stream.

Abaixo, vamos utilizar um Stream a partir da lista de produtos:

  List<MonetaryAmount> prices = products                .stream()                .map(product -> product.price)                .collect(Collectors.toList());

Como podemos ver, um mtodo chamado map fornecido pela interface Stream nos permitem mapear ou transformar cada item do Stream no valor solicitado, nesse caso, o preo de cada produto. No passo seguinte, instrumos o Stream para que, atravs do mtodo collect, colete o resultado como uma nova lista, e isso est sendo declarado atravs de uma instncia do tipo Collector fornecido atravs da chamada do mtodo toList da classe fbrica Collectors.

Do ponto de vista de execuo, voc pode pensar que cada mtodo, que representa uma operao, executada de maneira eager, ou seja, no momento da chamada da operao, mas isso no verdade para alguns metodos da interface Stream.

A interface Stream fornece as operaes intermedirias e operaes terminadoras.

As operaes intermedirias so operaes lazy, isto , elas s so performadas quando uma operao terminal executada.

J as operaes terminadoras so responsveis por efetivamente desencadear toda as operaes encadeadas atravs da interface fluente Stream.

O encadeamento dessas operaes ocorre com uma ou vrias operaes intermedirias em conjunto com uma operao terminal, formando assim o Stream Pipeline. Vamos conhecer mais sobre a interface Stream em blogposts futuros!

Em nosso exemplo, a operao map uma operao intermediria, e ela s ser disparada quando alguma operao terminal for executada, no nosso caso, a operao collect.

Agora, em nosso exemplo, no estamos lidando mais com vriaveis mutveis, e assim deixando declarativo o que queremos. No precisamos mais criar uma lista vazia. Todas essas preocupaes esto sendo controladas e delegadas para a implementao fornecida pela API do Java.

Mas podemos tambm utilizar no lugar da Lambda Expression um Method Reference, diminuindo ainda mais a chance de algum possvel erro na lgica de dentro da declarao da expresso. Vejamos como fica o resultado:

  List<MonetaryAmount> prices = products                .stream()                .map(Product::price)                .collect(Collectors.toList());

Talvez, voc deve estar se perguntando:

Quando devemos utilizar Method References?

Ns normalmente utilizamos muito mais lambda expressions do que method references quando estamos programando em Java. Mas isso no significa que method references no so importantes ou menos til.

Se uma lambda expression simplesmente repassa os parmetros para outro mtodo, mesmo sendo um mtodo de uma instncia ou esttico, podemos substitui-la pelo mtodo em questo, referenciando-o, por isso o nome method reference.

Alm de deixar o cdigo conciso do que utilizando lambda expressions, ao utilizar method reference ganhamos a habilidade de utilizar mtodos nomeados, e assim facilitar a compreenso do cdigo.

Mas e quanto a performance?

Como j sabemos, a linguagem Java j tem um longo caminho e usada em um grande nmero de aplicativos corporativos onde o desempenho crtico. Mas mesmo sabendo disso, muito razovel questionar se as novas features afetaro o desempenho.

A resposta sim, mas principalmente para melhor! Pode parecer ingnuo essa afirmao a princpio, mas antes de discutirmos sobre melhorias de desempenho, vamos lembrar as sbias palavras de Donald Knuth:

We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil.

Traduzindo:

Devemos esquecer as pequenas eficincias, digamos que cerca de 97% das vezes: a otimizao prematura a raiz de todo o mal.

Com isso em mente, devemos ser ousados em experimentar novos estilos onde eles podem fazer sentido. Se, ao utilizar o novo estilo e esse atender o desempenho adequado s necessidades da aplicao, ento podemos adot-lo e seguir em frente, caso contrrio, devemos avaliar de forma crtica o design de cdigo afim de encontrar os gargalos reais que o cdigo est apresentando.

As especificaes inseridas a partir do Java 8 fornecem um grande flexibilidade para facilitar as otimizaes do compilador. Essas otimizaes em conjunto com a instruo otimizada do bytecode InvokeDynamic podem fazer com que as chamadas utilizando Lambda Expressions sejam bem rpidas.

Vamos fazer um teste sobre performance.

Abaixo, temos um cdigo imperativo que contam os nmeros primos em uma coleo de nmeros:

  long count = 0;  for (long number : numbers) {      if (isPrime(number)) {          count++;      }  }

Utilizamos no examplo o habitual for loop para invocar um mtodo isPrime para determinar se cada nmero na coleo um nmero primo. Se ele for primo, ns incrementamos a varivel count. Vamos medir o tempo considerando uma lista de 1.000.000 (um milho) de nmeros.

  PT0.163687193S

Isso levou aproximadamente 0.163 segundos, mas vamos reduzir esse cdigo; vamos ver ver se utilizando um novo estilo que queremos adotar bate esse desempenho. Vamos refatorar o cdigo para o estilo funcional: onde o cdigo declarativo, criado favorecendo a imutabilidade, no tem efeitos colaterais, seguindo um encadeamento de funes de primeira ordem:

 numbers.stream()         .filter(i -> isPrime(i))         .count();

Aqui, transformamos a coleo em um Stream e ento aplicamos um filtro atravs de uma operao intermediria, filter, declarando que queremos somente os nmeros primos da coleo, e finalmente efetuamos o clculo ao performar a operao terminal count. Vamos ver quanto tempo essa verso levou para executar a mesma lgica na mesma coleo de 1.000.000 (um milho) de nmeros:

 PT0.167082778S

A sada, como podemos ver, utilizando lambda expression, foi aproximadamente o mesmo: 0.167 segundos; Pode parecer que no ganhamos ou perdemos nada, mas na verdade, ganhamos muito sim!

algo trivial paralelizar cdigos desenvolvidos utilizando o estilo funcional, agora paralelizar um cdigo imperativo, por outro lado, se ele j no foi arquitetado para esse fim, provavelmente no ser algo to trivial assim, no ?

Segue uma verso utilizando o estilo funcional e paralelizada de nosso cdigo:

  numbers.parallelStream()              .filter(i -> isPrime(i))              .count();

Com praticamente nenhum esforo, apenas chamando um outro default method chamado parallelStream da classe Collection, que tambm fornece uma instncia de Stream, habilitamos o paralelismo. Vamos ver se tivemos ganho de desempenho executando esse cdigo:

 PT0.055318673S

Executando essa verso paralelizada, em um processador com 8 ncleos, utilizando Java 17, levou aproximadamente 0.055 segundos para executar a tarefa.

Bom, brincadeiras parte, antes de comemorarmos essa performance, temos que admitir que um grande nmero de mtricas de desempenho podem ser artificiais e no podemos confiar cegamente neles. O que esse exemplo quer demonstrar nada mais que o uso de lambda expressions e o estilo funcional no significam desempenho ruim. Sempre ao criar cdigo real para aplicaes corporativas, devemos ficar de olho no desempenho e tratar as preocupaes onde elas surgirem.

E isso a pessoal ...

Estou curtindo demais ler o livro do renomado Venkat Subramaniam : "Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expression" 3, ento vou continuar a publicar mais blogposts com o intuido de fixar meu aprendizado e assim tambm ajudar quem interessar!

Espero que tenha gostado do texto!

Se gostou e achou relevante esse contedo, compartilhe com seus amigos.

Crticas e sugestes sero sempre bem-vindos!!!

No prximo blogpost, vamos conhecer mais sobre Stream API, seu funcionamento, seu pipeline, suas operaes e detalhes entre outras coisas...

At a prxima!

Source dos exemplos 4:

Referncias:

  1. Estou usando JSR 354 Java Money.

  2. Javadoc - Package java.util.stream

  3. Livro - "Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expression" by Venkat Subramaniam

  4. Estou usando JBang;


Original Link: https://dev.to/dearrudam/pt-br-transformando-listas-com-java-stream-api-4c2c

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To