JavaRush /Blogue Java /Random-PT /Tradução do livro. Programação funcional em Java. Capítul...
timurnav
Nível 21

Tradução do livro. Programação funcional em Java. Capítulo 1

Publicado no grupo Random-PT
Terei prazer em ajudá-lo a encontrar erros e melhorar a qualidade da tradução. Eu traduzo para melhorar minhas habilidades no idioma inglês e, se você ler e procurar erros de tradução, você melhorará ainda mais do que eu. O autor do livro escreve que o livro pressupõe muita experiência de trabalho com Java; para ser sincero, eu mesmo não tenho muita experiência, mas entendi o material do livro. O livro trata de alguma teoria que é difícil de explicar com os dedos. Se houver artigos decentes no wiki, fornecerei links para eles, mas para uma melhor compreensão recomendo que você mesmo pesquise no Google. boa sorte a todos. :) Para quem quiser corrigir minha tradução, bem como para quem achar que é muito ruim para ler em russo, você pode baixar o livro original aqui . Conteúdo Capítulo 1 Olá, Expressões Lambda - atualmente lendo Capítulo 2 Usando Coleções - em desenvolvimento Capítulo 3 Strings, Comparadores e Filtros - em desenvolvimento Capítulo 4 Desenvolvimento com Expressões Lambda - em desenvolvimento Capítulo 5 Trabalhando com Recursos - em desenvolvimento Capítulo 6 Sendo Preguiçoso - em desenvolvimento Capítulo 7 Otimizando recursos - em desenvolvimento Capítulo 8 Layout com expressões lambda - em desenvolvimento Capítulo 9 Juntando tudo - em desenvolvimento

Capítulo 1 Olá, Expressões Lambda!

Nosso código Java está pronto para transformações notáveis. As tarefas diárias que realizamos tornam-se mais simples, fáceis e expressivas. A nova forma de programar Java é usada há décadas em outras linguagens. Com essas mudanças no Java, podemos escrever código conciso, elegante e expressivo com menos erros. Podemos usar isso para aplicar padrões facilmente e implementar padrões de design comuns com menos linhas de código. Neste livro, exploramos o estilo funcional de programação usando exemplos simples de problemas que resolvemos todos os dias. Antes de mergulharmos nesse estilo elegante e nessa nova forma de desenvolver software, vamos ver por que ele é melhor.
Mude seu pensamento
Estilo imperativo é o que Java nos deu desde o início da linguagem. Esse estilo sugere que descrevamos para Java cada etapa do que queremos que a linguagem faça e, então, simplesmente nos certifiquemos de que essas etapas sejam seguidas fielmente. Isso funcionou muito bem, mas ainda é de baixo nível. O código acabou sendo muito detalhado e muitas vezes queríamos uma linguagem um pouco mais inteligente. Poderíamos então dizê-lo declarativamente - o que queremos, e não nos aprofundarmos em como fazê-lo. Graças aos desenvolvedores, o Java agora pode nos ajudar a fazer isso. Vejamos alguns exemplos para entender os benefícios e diferenças entre essas abordagens.
A maneira usual
Vamos começar com princípios básicos para ver os dois paradigmas em ação. Isso usa um método imperativo para procurar Chicago na coleção de cidades - as listagens neste livro mostram apenas trechos de código. boolean found = false; for(String city : cities) { if(city.equals("Chicago")) { found = true; break; } } System.out.println("Found chicago?:" + found); A versão imperativa do código é barulhenta (o que essa palavra tem a ver com isso?) e de baixo nível, existem várias partes mutáveis. Primeiro, criamos esse sinalizador booleano fedorento chamado encontrado e, em seguida, iteramos sobre cada elemento da coleção. Se encontrarmos a cidade que procuramos, definimos a bandeira como verdadeira e quebramos o loop. Finalmente imprimimos o resultado da nossa pesquisa no console.
Há uma maneira melhor
Como programadores Java observadores, uma rápida olhada neste código pode transformá-lo em algo mais expressivo e mais fácil de ler, como este: System.out.println("Found chicago?:" + cities.contains("Chicago")); Aqui está um exemplo do estilo declarativo - o método contains() nos ajuda a chegar diretamente ao que precisamos.
Mudanças reais
Essas mudanças trarão uma quantidade razoável de melhorias ao nosso código:
  • Sem problemas com variáveis ​​mutáveis
  • As iterações de loop estão escondidas sob o capô
  • Menos confusão de código
  • Maior clareza de código, concentra a atenção
  • Menos impedância; o código acompanha de perto a intenção do negócio
  • Menos chance de erro
  • Mais fácil de entender e apoiar
Além dos casos simples
Este foi um exemplo simples de função declarativa que verifica a presença de um elemento em uma coleção; ela é usada há muito tempo em Java. Agora imagine não ter que escrever código imperativo para operações mais avançadas como analisar arquivos, trabalhar com bancos de dados, fazer solicitações de serviços web, criar multithreading, etc. Java agora torna possível escrever código conciso e elegante que torna mais difícil cometer erros, não apenas em operações simples, mas em toda a nossa aplicação.
A maneira antiga
Vejamos outro exemplo. Estamos criando uma coleção com preços e tentaremos diversas formas de calcular a soma de todos os preços com desconto. Suponha que nos peçam para somar todos os preços cujo valor exceda US$ 20, com um desconto de 10%. Vamos primeiro fazer isso da maneira usual em Java. Este código deve ser muito familiar para nós: primeiro criamos uma variável mutável totalOfDiscountedPrices na qual armazenaremos o valor resultante. Em seguida, percorremos a coleção de preços, selecionamos preços acima de US$ 20, obtemos o preço com desconto e adicionamos esse valor a totalOfDiscountedPrices . No final exibimos a soma de todos os preços levando em consideração o desconto. Abaixo está o que é enviado para o console final List prices = Arrays.asList( new BigDecimal("10"), new BigDecimal("30"), new BigDecimal("17"), new BigDecimal("20"), new BigDecimal("15"), new BigDecimal("18"), new BigDecimal("45"), new BigDecimal("12")); BigDecimal totalOfDiscountedPrices = BigDecimal.ZERO; for(BigDecimal price : prices) { if(price.compareTo(BigDecimal.valueOf(20)) > 0) totalOfDiscountedPrices = totalOfDiscountedPrices.add(price.multiply(BigDecimal.valueOf(0.9))); } System.out.println("Total of discounted prices: " + totalOfDiscountedPrices);
Total de preços com desconto: 67,5
Funciona, mas o código parece confuso. Mas a culpa não é nossa, usamos o que estava disponível. O código é de nível bastante baixo - ele sofre de uma obsessão por primitivos (pesquise no Google, coisas interessantes) e vai contra o princípio da responsabilidade única . Aqueles de nós que trabalham em casa devem manter esse código longe dos olhos das crianças que aspiram a se tornar programadores, pois isso pode alarmar suas mentes frágeis, estar preparados para a pergunta "É isso que você precisa fazer para sobreviver?"
Há uma maneira melhor, outra
Agora podemos fazer melhor, muito melhor. Nosso código pode parecer um requisito de especificação. Isso nos ajudará a reduzir a lacuna entre as necessidades do negócio e o código que as implementa, reduzindo ainda mais a probabilidade de os requisitos serem mal interpretados. Em vez de criar uma variável e depois alterá-la repetidamente, vamos trabalhar em um nível mais alto de abstração, como na listagem a seguir. final BigDecimal totalOfDiscountedPrices = prices.stream() .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0) .map(price -> price.multiply(BigDecimal.valueOf(0.9))) .reduce(BigDecimal.ZERO, BigDecimal::add); System.out.println("Total of discounted prices: " + totalOfDiscountedPrices); Vamos ler em voz alta - o filtro de preço é maior que 20, mapeie (crie pares de "chave" "valor") usando a chave "preço", preço incluindo desconto e, em seguida, adicione-os
- comentário do tradutor significa palavras que aparecem na sua cabeça durante a leitura do código .filter(price -> price.compareTo(BigDecimal.valueOf(20)) > 0)
O código é executado junto na mesma sequência lógica que lemos. O código foi encurtado, mas usamos um monte de coisas novas do Java 8. Primeiro, chamamos o método stream() na lista de preços . Isso abre a porta para um iterador personalizado com um rico conjunto de recursos convenientes que discutiremos mais tarde. Em vez de percorrer diretamente todos os valores da lista de preços , usamos vários métodos especiais, como filter() e map() . Ao contrário dos métodos que usamos em Java e no JDK, esses métodos usam uma função anônima - uma expressão lambda - como parâmetro entre parênteses. Estudaremos isso com mais detalhes posteriormente. Chamando o método reduz() , calculamos a soma dos valores (preço com desconto) obtidos no método map() . O loop fica oculto da mesma forma que ao usar o método contains() . Os métodos filter() e map() são, entretanto, ainda mais complexos. Para cada preço na lista de preços , eles chamam a função lambda passada e a salvam em uma nova coleção. O método reduzir() é chamado nesta coleção para produzir o resultado final. Abaixo está o que é enviado para o console
Total de preços com desconto: 67,5
Mudanças
Abaixo estão as alterações em relação ao método usual:
  • O código é agradável à vista e não confuso.
  • Sem operações de baixo nível
  • Mais fácil de melhorar ou alterar a lógica
  • A iteração é controlada por uma biblioteca de métodos
  • Avaliação de loop eficiente e preguiçoso
  • Mais fácil de paralelizar conforme necessário
Discutiremos mais tarde como o Java fornece essas melhorias.
Lambda para o resgate :)
Lambda é a chave funcional para nos libertar das dificuldades da programação imperativa. Ao mudar a forma como programamos, com os recursos mais recentes do Java, podemos escrever código que não é apenas elegante e conciso, mas também menos sujeito a erros, mais eficiente e mais fácil de otimizar, melhorar e tornar multithread.
Ganhe muito com a programação funcional
O estilo de programação funcional possui uma relação sinal-ruído mais alta ; Escrevemos menos linhas de código, mas cada linha ou expressão executa mais funcionalidades. Ganhamos pouco com a versão funcional do código em comparação com o imperativo:
  • Evitamos alterações indesejadas ou reatribuições de variáveis, que são fonte de erros e dificultam o processamento de código de diferentes threads ao mesmo tempo. Na versão imperativa, definimos valores diferentes para a variável totalOfDiscountedPrices ao longo do loop . Na versão funcional, não há alteração explícita da variável no código. Menos alterações levam a menos bugs no código.
  • A versão funcional do código é mais fácil de paralelizar. Mesmo que os cálculos no método map() sejam demorados, podemos executá-los em paralelo sem medo de nada. Se acessarmos o código de estilo imperativo de threads diferentes, precisaremos nos preocupar em alterar a variável totalOfDiscountedPrices ao mesmo tempo . Na versão funcional, acessamos a variável somente após todas as alterações terem sido feitas, isso nos livra de preocupações com a segurança do thread do código.
  • O código é mais expressivo. Em vez de executar o código em várias etapas - criando e inicializando uma variável com um valor fictício, percorrendo a lista de preços, adicionando preços com desconto à variável e assim por diante - simplesmente pedimos ao método map() da lista para retornar outra lista de preços com desconto e adicioná -los .
  • O estilo funcional é mais conciso: são necessárias menos linhas de código do que a versão imperativa. Código mais compacto significa menos para escrever, menos para ler e mais fácil de manter.
  • A versão funcional do código é intuitiva e fácil de entender, desde que você conheça sua sintaxe. O método map() aplica a função passada (que calcula o preço com desconto) a cada elemento da coleção e produz uma coleção com o resultado, como podemos ver na imagem abaixo.

Figura Figura 1 - o método map aplica a função passada a cada elemento da coleção
Com o suporte de expressões lambda, podemos aproveitar totalmente o poder do estilo funcional de programação em Java. Se dominarmos esse estilo, poderemos criar um código mais expressivo e conciso, com menos alterações e erros. Anteriormente, uma das principais vantagens do Java era o suporte ao paradigma orientado a objetos. E o estilo funcional não contradiz OOP. Excelência real na passagem da programação imperativa para a declarativa. Com Java 8 podemos combinar programação funcional com um estilo orientado a objetos de forma bastante eficaz. Podemos continuar a aplicar o estilo OO aos objetos, seu escopo, estado e relacionamentos. Além disso, podemos modelar o comportamento e o estado de mudança, os processos de negócios e o processamento de dados como uma série de conjuntos de funções.
Por que codificar em estilo funcional?
Vimos os benefícios gerais do estilo funcional de programação, mas vale a pena aprender esse novo estilo? Será uma pequena mudança na linguagem ou mudará nossas vidas? Devemos obter respostas a estas perguntas antes de desperdiçarmos o nosso tempo e energia. Escrever código Java não é tão difícil; a sintaxe da linguagem é simples. Estamos confortáveis ​​com bibliotecas e APIs familiares. O que realmente exige que nos esforcemos para escrever e manter código são os aplicativos corporativos típicos onde usamos Java para desenvolvimento. Precisamos ter certeza de que os colegas programadores fechem as conexões com o banco de dados no momento correto, que não as mantenham ou realizem transações por mais tempo do que o necessário, que capturem as exceções completamente e no nível correto, que apliquem e liberem bloqueios adequadamente. ... esta folha pode continuar por muito tempo. Cada um dos argumentos acima por si só não tem peso, mas em conjunto, quando combinados com as inerentes complexidades de implementação, tornam-se esmagadores, demorados e difíceis de implementar. E se pudéssemos encapsular essas complexidades em pequenos pedaços de código que também pudessem gerenciá-las bem? Assim, não gastaríamos energia constantemente na implementação de normas. Isso daria uma grande vantagem, então vamos ver como um estilo funcional pode ajudar.
Joe pergunta
Um código curto* significa simplesmente menos letras de código?
* estamos falando da palavra conciso , que caracteriza o estilo funcional de código usando expressões lambda
Neste contexto, o código deve ser conciso, sem frescuras e reduzido ao impacto direto para transmitir a intenção de forma mais eficaz. Esses são benefícios de longo alcance. Escrever código é como juntar ingredientes: torná-lo conciso é como adicionar molho a ele. Às vezes é preciso mais esforço para escrever esse código. Menos código para ler, mas torna o código mais transparente. É importante manter o código claro ao encurtá-lo. Código conciso é semelhante a truques de design. Este código requer menos dança com pandeiro. Isto significa que podemos implementar rapidamente as nossas ideias e seguir em frente se funcionarem e abandoná-las se não corresponderem às expectativas.
Iterações com esteróides
Usamos iteradores para processar listas de objetos, bem como para trabalhar com Conjuntos e Mapas. Os iteradores que usamos em Java nos são familiares; embora sejam primitivos, não são simples. Eles não apenas ocupam várias linhas de código, mas também são bastante difíceis de escrever. Como iteramos todos os elementos de uma coleção? Poderíamos usar um loop for. Como selecionamos alguns elementos da coleção? Usando o mesmo loop for, mas usando algumas variáveis ​​mutáveis ​​adicionais que precisam ser comparadas com algo da coleção. Então, após selecionar um valor específico, como realizamos operações sobre um único valor, como mínimo, máximo ou algum valor médio? Novamente ciclos, novamente novas variáveis. Isso lembra o provérbio, você não pode ver as árvores por causa da floresta (o original usa um jogo de palavras relacionado a iterações e significa “Tudo é assumido, mas nem tudo dá certo” - nota do tradutor). O jdk agora fornece iteradores internos para várias instruções: um para simplificar o loop, um para vincular a dependência de resultado necessária, um para filtrar os valores de saída, um para retornar os valores e várias funções de conveniência para obter mínimo, máximo, médias, etc. Além disso, a funcionalidade dessas operações pode ser combinada facilmente, para que possamos combinar diferentes conjuntos delas para implementar a lógica de negócios com maior facilidade e menos código. Quando terminarmos, o código será mais fácil de entender, pois cria uma solução lógica na sequência exigida pelo problema. Veremos alguns exemplos desse tipo de código no Capítulo 2 e posteriormente neste livro.
Aplicação de algoritmos
Algoritmos impulsionam aplicativos corporativos. Por exemplo, precisamos fornecer uma operação que exija verificação de autoridade. Teremos que garantir que as transações sejam concluídas rapidamente e que as verificações sejam concluídas corretamente. Tais tarefas são muitas vezes reduzidas a um método muito comum, como na listagem abaixo: Transaction transaction = getFromTransactionFactory(); //... Операция выполняющаяся во время транзакции... checkProgressAndCommitOrRollbackTransaction(); UpdateAuditTrail(); Existem dois problemas com esta abordagem. Primeiro, isto muitas vezes leva a uma duplicação do esforço de desenvolvimento, o que por sua vez leva a um aumento no custo de manutenção da aplicação. Em segundo lugar, é muito fácil perder exceções que podem ser lançadas no código desta aplicação, comprometendo assim a execução da transação e a passagem dos cheques. Podemos usar um bloco try-finally adequado, mas toda vez que alguém tocar nesse código, precisaremos verificar novamente se a lógica do código não foi quebrada. Caso contrário, poderíamos abandonar a fábrica e virar todo o código de cabeça para baixo. Em vez de receber transações, poderíamos enviar o código de processamento para uma função bem gerenciada, como o código abaixo. runWithinTransaction((Transaction transaction) -> { //... Операция выполняющаяся во время транзакции... }); Essas pequenas alterações resultam em enormes economias. O algoritmo para verificação de status e verificações de aplicativos recebe um novo nível de abstração e é encapsulado usando o método runWithinTransaction() . Neste método colocamos um trecho de código que deve ser executado no contexto de uma transação. Não precisamos mais nos preocupar em esquecer de fazer alguma coisa ou se pegamos a exceção no lugar certo. Funções algorítmicas cuidam disso. Esta questão será discutida com mais detalhes no Capítulo 5.
Extensões de algoritmo
Algoritmos estão sendo usados ​​cada vez com mais frequência, mas para que possam ser totalmente utilizados no desenvolvimento de aplicações empresariais, são necessárias formas de estendê-los.
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION