Baixe o app para aproveitar ainda mais
Prévia do material em texto
2 InfoQ Brasil Onde foi parar o PermGen do Java? 8 funcionalidades pouco conhecidas do Java 8 Anotações de tipos no Java 8: Ferramentas e oportunidades Java 8: Desmistificando Lambdas Quão funcional é o Java 8? Java 7 - Características que viabilizam o Java 8 Q&A: Novidades do Java 8 Do Groovy ao Java 8 Nashorn: Combinando o poder do Java e JavaScript no JDK 8 Java 9 e além: Brian Goetz e John Rose falam sobre o futuro do Java Novos Horizontes do Java EMAG | JAVA: PRESENTE E FUTURO 05. 11. 15. 22. 29. 33. 36. 38. 42. 49. 51. Onde foi parar o PermGen do Java? . . . . . . . . . . . . . . . . . . . . 5 Adeus PermGen. Olá Metaspace! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 Mudança para o Metaspace e suas alocações . . . . . . . . . . . . . . . . 6 Otimização e Metaspace . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Questões Atuais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 8 funcionalidades pouco conhecidas do Java 8 . . .11 1. StampedLock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .11 2. Adicionadores concorrentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .12 3. Ordenação Paralela . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .12 4. Migrando para nova API de data . . . . . . . . . . . . . . . . . . . . . . . . . . .12 5. Controle de processos do sistema operacional . . . . . . . . . . .13 6. Operações numéricas precisas . . . . . . . . . . . . . . . . . . . . . . . . . . . . .13 7. Geração segura de números aleatórios . . . . . . . . . . . . . . . . . . .13 8. Referências opcionais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .13 Anotações de tipos no Java 8: Ferramentas e oportunidades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .15 Sintaxe das anotações de tipo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .16 Detectando erros com anotações . . . . . . . . . . . . . . . . . . . . . . . . . . . .17 O framework Checker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .18 Aumentando a produtividade com anotações de tipo . . . . .19 A estrada à frente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .20 Conclusão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .21 Java 8: Desmistificando Lambdas . . . . . . . . . . . . . . . . . . . . . . .22 Referência de métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .24 Evolução da biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .25 Operações de agregação . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .25 Fontes de stream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .26 Operações de finalização de Stream . . . . . . . . . . . . . . . . . . . . . . . . .26 Inteface Iterable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .26 Conclusão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .28 Quão funcional é o Java 8? . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 O que é uma linguagem de programação funcional?. . . . . 29 Programação funcional em linguagens não funcionais . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 Java - um pouco de história . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 O sistema de tipos original do Java . . . . . . . . . . . . . . . . . . . . . . 30 Alternativas a tipos nomeados . . . . . . . . . . . . . . . . . . . . . . . . . . 31 O sistema de tipos do Java 5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 Recursos introduzidos no Java 6 e 7 . . . . . . . . . . . . . . . . . . . . . 31 O sistema de tipos do Java 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Quão funcional é o Java 8? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Java 7: Características que viabilizam o Java 8 . . . 33 Operador Diamante. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 Manipulador de Métodos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 invokedynamic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Conclusão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Q&A: Novidades do Java 8 . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 Do Groovy ao Java 8. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 Nashorn: Combinando o poder do Java e JavaScript no JDK 8. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Shell scripts. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 Passando dados para o Java e vice-versa. . . . . . . . . . . . . . . . . 45 Usando classes Java no Nashorn. . . . . . . . . . . . . . . . . . . . . . . . . 45 Linguagem funcional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 Dialeto especial do JavaScript no Nashorn . . . . . . . . . . . . . . . 46 Avatar.js . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 Resumindo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 Java 9 e além: Brian Goetz e John Rose falam sobre o futuro do Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Novos Horizontes do Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 Primeiros anúncios . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 Outras novidades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 Data final quase definida, com alguns sacrifícios . . . . . . . . . 52 CONTEÚDO DETALHADO 4 InfoQ Brasil O Java 8 é o release atual do Java desde 2014, mas para alguns ainda é um sonho que o legado não deixa se concretizar. Por outro lado, ambientes empresariais e grupos de desenvolve- dores estão se modernizando e gradualmente tornando o novo Java mainstream. Com o Java 9 planejado para o primeiro semestre de 2017, a versão 8 da linguagem/plataforma mais popular do mundo é a realidade que queremos conhecer e alcançar hoje. Com essa eMag, buscamos oferecer uma base para você explorar as muitas novidades do Java 8 – de novas APIs a mudanças na linguagem. Também vis- lumbramos um pouco do futuro, concluindo essa coleção de artigos do InfoQ com um apanhadodo que está por vir no Java 9. O Java 8 contém novas funcionalidades, aprimoramentos e corre- ções de bugs para maior eficiência no desenvolvimento e na execução; traz features voltadas ao aumento de produtividade e facilidade de uso, além de melhorias em programação poliglota, segurança e per- formance. Se as versões 6 e 7 do Java foram alterações consideradas leves, o Java 8 está em outro patamar. Mais de 56 novas funciona- lidades foram adicionadas. Chegaram os lambdas, métodos padrão, interfaces funcionais e a API de streams, modificando radicalmente a linguagem e portanto todo o ecossistema Java. Há ainda uma nova API para datas, novas anotações e aumento de desempenho do engi- ne para JavaScript. Neste eMag, o InfoQ leva praticantes numa visita através do Java 8 e as futuras versões da linguagem, explorando como chegamos até aqui e algumas formas que seguiremos adiante com a tecnologia. APRESENTAÇÃO 5eMag | Java: Presente e Futuro ONDE FOI PARAR O PERMGEN DO JAVA? por Monica Beckwith, traduzido por Rafael Brito Com a introdução do JDK8, não existe mais o espaço de PermGen. Os metadados que antes eram armazenados no PermGen não desapareceram, mas foram movidos para a memória nativa, em uma área conhecida como “Metaspace”. Conheça neste artigo maiores detalhes desta importante mudança da plataforma Java. A Máquina Virtual do Java (JVM) utiliza uma represen-tação interna de suas classes contendo uma série de metadados por classe, tais como: informações de hie- rarquia de classes, dados e informa- ções de métodos (tais como byteco- des, pilha e tamanho de variáveis), o pool contínuo em tempo de execução, resoluções de referências simbólicas e Vtables. Anteriormente, quando os class- loaders customizados não eram tão comuns, as classes eram “estáticas” em sua maioria, raramente eram des- carregadas ou coletadas, e consequen- temente eram marcadas como “per- manentes”. Além disso, desde que as classes fizessem parte da implemen- tação da JVM, e não fossem criadas pela aplicação, elas eram considera- das como “memória não pertencente ao heap”. Na JVM HotSpot antecessora ao JDK8, a representação “permanente” ficava em uma área chamada per- manent generation (“geração perma- nente”). A geração permanente era contígua ao heap do Java e limitada a -XX:MaxPermSize, que precisava ser configurada por linha de comando antes de iniciar a JVM, caso contrá- rio seria iniciada com o valor padrão de 64M (85M para 64bit scaled poin- ters - ponteiros auto-incrementáveis com deslocamentos constantes, que facilitam o acesso à estrutura). A co- leção da geração permanente ficaria associada à coleção da antiga gera- ção, então sempre que alguma delas ficassem cheias, ambas as gerações (permanentes e antigas) seriam cole- tadas. Um dos problemas mais óbvios que se pode verificar de imediato é a dependência do -XX:MaxPermSize. Se o tamanho das classes de metadados estiver além dos limites do -XX:Max- PermSize, sua aplicação será execu- tada com pouco espaço de memória e um erro de “Out of Memory” será apresentado. Curiosidade: Antes do JDK7, para a JVM HotSpot, as Strings internali- zadas (interned) também eram manti- das na geração permanente, também conhecida como PermGen, causan- do muitos problemas e erros de Out of Memory. Para mais informações acesse a documentação desse proble- ma. 6 InfoQ Brasil Com o surgimento do JDK8, Perm- Gen não existe mais. As informações de metadados não desapareceram, só que o espaço em que eram mantidos não é mais contíguo ao heap. Agora, o metadado foi movido para a memória nativa, em uma área conhecida como “Metaspace”. A mudança para o Metaspace foi necessária, pois era realmente difícil de se fazer o ajuste fino (tunning) do PermGen. Havia ainda a possibilida- de dos metadados serem ser movidos por qualquer coleta de lixo completa. Além disso, era difícil dimensionar o PermGen, uma vez que o seu ta- manho dependia de muitos fatores, tais como: o número total de classes, tamanho dos pools constantes, tama- nhos dos métodos etc. Adicionalmente, cada garbage col- lector na HotSpot precisava de um có- digo especializado para lidar com os metadados no PermGen. Desacoplar os metadados do PermGen não só permite o gerenciamento contínuo do Metaspace, como também melhorias, tais como: simplificação da coleta de lixo completa e futura desalocação concorrente de metadados de classe. Mudança para o Metaspace e suas alocações Agora a VM Metaspace (Máquina Virtual Metaspace) emprega técnicas de gerenciamento de memória para gerenciar o Metaspace. Movendo as- sim o trabalho de diferentes coletores de lixo para uma única VM Metas- pace. Uma questão por trás do Me- taspace reside no fato de que o ciclo de vida das classes e seus metadados corresponde ao ciclo de vida dos clas- sloaders. Isto é, quanto mais tempo o classloader estiver ativo, mais o meta- dado permanecerá ativo no Metaspa- ce e não poderá ser liberado. Temos usado o termo “Metaspace” de forma muito vaga neste texto. Mais formalmente, a área de armazenagem por classloader é chamado de “me- taspace”, e estes metaspaces são co- letivamente chamados “Metaspace”. A reciclagem ou recuperação de me- taspace por classloader pode ocorrer somente depois que o seu classloader não estiver mais ativo e foi relatado como inativo pelo coletor de lixo. Não há realocação ou compactação nestes metaspaces, mas os metadados são varridos por referências Java. A VM Metaspace gerencia a alo- cação de Metaspace empregando um alocador de segmentos. O tamanho dos segmentos depende do tipo de classloader. Há uma lista global de segmentos livres. Sempre que um classloader precisa de um segmento, remove-o da lista global e mantém a sua própria lista de segmentos. Quan- do algum classloader é finalizado, seus segmentos são liberados e retor- nam novamente para a lista global de segmentos livres. Esses segmentos são posteriormente divididos em blo- cos e cada bloco contém uma unidade de metadados. A alocação de blocos dos segmentos é linear (ponteiro de colisão). Os segmentos são alocados fora dos espaços de memória mape- ada (mmapped). Há uma lista ligada para os tais espaços globais mmapped virtuais, e sempre que algum espaço virtual é esvaziado, ele é devolvido ao sistema operacional. Adeus PermGen. Olá Metaspace! 7eMag | Java: Presente e Futuro A figura acima mostra a alocação de Metaspace com metasegmentos em um espaço virtual mmapped. Os Classloaders 1 e 3 representam a re- flexão ou classloaders anônimos e empregam segmentos de tamanhos “específicos”. Os Classloaders 2 e 4 podem ser empregados em segmen- tos de tamanho pequeno ou médio, baseado no número de itens em seus carregadores. Como mencionado anteriormen- te, uma VM Metaspace gerenciará o crescimento do Metaspace, mas talvez surgirão cenários em que se deseje limitar o crescimento através do uso da configuração explícita do -XX:MaxMetaspaceSize via linha de comando. Por padrão, o -XX:MaxMe- taspaceSize não possui um limite, Otimização e Metaspace então, tecnicamente, o tamanho do Metaspace poderia assumir o espaço de swap, e começaria a gerar falhas de alocação nativa. Para uma JVM servidora de 64 bits, o valor padrão/inicial do -XX:Metas- paceSize é de 21MB. Esta é a configu- ração do limite máximo inicial. Uma que vez que esta marca é alcançada, uma varredura de lixo completa é disparada para descarregar classes (quando os seus classloaders não esti- verem mais ativos), e o limite máximo é reiniciado. O novo valor deste limite depende da quantidade de Metaspace liberado. Se o espaço liberado não for suficiente, o limite aumenta; se for li- berado espaço demais, o limite dimi- nui. Isto serárepetido diversas vezes se o limite inicial for muito baixo, e será possível constatar a repetida co- leta de lixo completa nos logs de co- leta de lixo. Em tal cenário, recebe-se o aviso para configurar o -XX:Metas- paceSize para um valor mais elevado através da linha de comando, para evitar as coletas de lixo iniciais. Após coletas subsequentes, a VM Metaspace será automaticamente ajustada para o limite mais alto, de modo a postergar a coleta de lixo do Metaspace. Há também duas opções: -XX:Min- MetaspaceFreeRatio e -XX:MaxMetas- paceFreeRatio, que são análogas aos parâmetros do GC FreeRatio e podem ser configurados através da linha de comando. Algumas ferramentas foram mo- dificadas para ajudar a obter mais informações sobre o Metaspace, e são listadas a seguir: 8 InfoQ Brasil • jcmd <PID> GC.class_stats: Este é um novo comando de diagnóstico que permite ao usuário final se conectar à JVM em execução, e obter um histograma detalhado dos metadados das classes Java. Nota: Com o JDK8 build 13, você deve iniciar o Java com -XX:+UnlockDiagnosticVMOptions. • jstat -gc <LVMID>: agora exibe informações do Metaspace, como demonstra o exemplo a seguir: $ jmap -clstats <PID> Attaching to process ID 6476, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.5-b02 finding class loader instances ..done. computing per loader stat ..done. please wait.. computing liveness.liveness analysis may be inaccurate ... class_loader classes bytes parent_loader alive? type <bootstrap> 655 1222734 null live <internal> 0x000000074004a6c0 0 0 0x000000074004a708 dead java/util/ResourceBundle$RBClassLoader@0x00000007c0053e20 0x000000074004a760 0 0 null dead sun/misc/Launcher$ExtClassLoader@0x00000007c002d248 0x00000007401189c8 1 1471 0x00000007400752f8 dead sun/reflect/DelegatingClassLoader@0x00000007c0009870 0x000000074004a708 116 316053 0x000000074004a760 dead sun/misc/Launcher$AppClassLoader@0x00000007c0038190 0x00000007400752f8 538 773854 0x000000074004a708 dead org/dacapo/harness/DacapoClassLoader@0x00000007c00638b0 total = 6 1310 2314112 N/A alive=1, dead=5 N/A $jstat-gc <LVMID> SOC SIC SOU S1U EC EU OC OU MC UM CCSC CCSU YGC YGCT FGC FGCT GCT 1024.0 1024.0 0.0 96.0 6144.0 2456.3 129536.0 2228.3 7296.0 6550.3 896.0 787.0 229 0.211 33 0.347 0.558 Where, MC: Current Metaspace Capacity (KB); UM: Metaspace Utilization (KB) $ jcmd <PID> help GC.class_stats 9522: GC.class_stats Provide statistics about Java class meta data. Requires -XX:+UnlockDiagnosticVMOptions. Impact: High: Depends on Java heap size and content. Syntax : GC.class_stats [options] [<columns>] Arguments: columns : [optional] Comma-separated list of all the columns to show. If not specified, the following columns are shown: InstBytes,KlassBytes,CpAll,annotations,MethodCount,Bytecodes,MethodAll,ROAll,RWAll,Total (STRING, no default value) Options: (options must be specified using the <key> or <key>=<value> syntax) -all : [optional] Show all columns (BOOLEAN, false) -csv : [optional] Print in CSV (comma-separated values) format for spreadsheets (BOOLEAN, false) -help : [optional] Show meaning of all the columns (BOOLEAN, false) • jmap -clstats <PID>: exibe as estatísticas de um classloader. (Isto agora assume o lugar do -permstat, que era usado para exibir estatísticas do classloader de JVMs anteriores ao JDK8). Segue um exemplo de saída no momento de execução do benchmark da DaCapo Avrora: 9eMag | Java: Presente e Futuro Um exemplo de saída: $ jcmd <PID> GC.class_stats 7140: Index Super InstBytes KlassBytes annotations CpAll MethodCount Bytecodes MethodAll ROAll RWAll Total ClassName 1 -1 426416 480 0 0 0 0 0 24 576 600 [C 2 -1 290136 480 0 0 0 0 0 40 576 616 [Lavro- ra.arch.legacy.LegacyInstr; 3 -1 269840 480 0 0 0 0 0 24 576 600 [B 4 43 137856 648 0 19248 129 4886 25288 16368 30568 46936 java.lang.Class 5 43 136968 624 0 8760 94 4570 33616 12072 32000 44072 java.lang.String 6 43 75872 560 0 1296 7 149 1400 880 2680 3560 java. util.HashMap$Node 7 836 57408 608 0 720 3 69 1480 528 2488 3016 avrora.sim.util. MulticastFSMProbe 8 43 55488 504 0 680 1 31 440 280 1536 1816 avrora. sim.FiniteStateMachine$State 9 -1 53712 480 0 0 0 0 0 24 576 600 [Ljava. lang.Object; 10 -1 49424 480 0 0 0 0 0 24 576 600 [I 11 -1 49248 480 0 0 0 0 0 24 576 600 [Lavrora.sim.pla- tform.ExternalFlash$Page; 12 -1 24400 480 0 0 0 0 0 32 576 608 [Ljava.util.HashMa- p$Node; 13 394 21408 520 0 600 3 33 1216 432 2080 2512 avrora.sim.AtmelIn- terpreter$IORegBehavior 14 727 19800 672 0 968 4 71 1240 664 2472 3136 avrora.arch.legacy. LegacyInstr$MOVW …<snipped> …<snipped> 1299 1300 0 608 0 256 1 5 152 104 1024 1128 sun.util.resources. LocaleNamesBundle 1300 1098 0 608 0 1744 10 290 1808 1176 3208 4384 sun.util.resources. OpenListResourceBundle 1301 1098 0 616 0 2184 12 395 2200 1480 3800 5280 sun.util.resources. ParallelListResourceBundle 2244312 794288 2024 2260976 12801 561882 3135144 1906688 4684704 6591392 Total 34.0% 12.1% 0.0% 34.3% - 8.5% 47.6% 28.9% 71.1% 100.0% Index Super InstBytes KlassBytes annotations CpAll MethodCount Bytecodes MethodAll ROAll RWAll Total ClassName Como mencionado anteriormente, a VM Metaspace emprega um alocador de segmentos. Há múltiplos tamanhos de segmentos, dependendo do tipo de classloader. Além disso, os itens de classe por si mesmos não possuem um tamanho fixo, por isso há chances de que os segmentos livres não sejam do mesmo tamanho que os segmentos necessários para os itens de classe. Tudo isso pode gerar uma fragmentação. A VM Metaspace (ainda) não utiliza compactação, consequente- mente a fragmentação é uma das principais preocupações neste momento. Questões Atuais 10 InfoQ Brasil Monica Beckwith é uma engenheira de desempenho trabalhando na indústria de hardware por mais de uma década. Seu mais atual título é Arquiteta de Desempenho na Servergy - uma startup que oferece uma nova classe de servidores Cleantech hiper-eficientes ®. Antes da Servergy, Monica trabalhou na Oracle / Sun e AMD otimizando a JVM para sistemas de classe de servidor. Monica foi eleita uma palestrante “estrela do rock” no JavaOne 2013. Sobre a Autora Acompanhe Monica no Twitter: @mon_beck. 11eMag | Java: Presente e Futuro 8 FUNCIONALIDADES POUCO CONHECIDAS DO JAVA 8 por Tal Weiss, traduzido por Vitor Puente Este artigo visa apresentar algumas das novas APIs do Java 8 que não estão sendo tão comentadas, mas que tornaram o Java melhor de várias maneiras. O código multithreaded tem sido um desafio para os desenvolvedores. Com o tempo, algumas construções complexas foram adicionadas às bibliotecas Java visando auxiliar o desenvolvedor com códigos multithreaded, e seu acesso a recursos compartilhados. Um exemplo é o ReadWriteLock, que permite ao desenvolvedor dividir o código em seções que precisam ser mutuamente exclusi- vas (“writers”) e seções que não precisam (“readers”).Na teoria parece ótimo. Porém um problema com Re- adWriteLock é que a aplicação pode ficar muito lenta, às vezes até dez vezes mais lenta. O Java 8 introduziu um novo bloqueio ReadWrite, chamado StampedLock. A boa notícia é que o StampedLock é muito rápido. A má notícia é que é mais complicado de usar e envolve mais estados. O StampedLock não é “reentrante”, o que significa que uma 1. StampedLock thread pode causar um deadlock envolvendo ela mesma. O StampedLock possui o modo “otimista”, que emite uma marcação (stamp) retornada por cada operação de bloqueio (lock). Essa marcação é utilizada como uma es- pécie de ingresso. Cada operação de desbloqueio (unlo- ck) precisa gerar sua marcação correspondente. Qualquer thread que adquirir o bloqueio de escrita, enquanto outra thread está com o bloqueio de escrita do modo otimista, fará com que o unlock seja invalidado (o stamp não será mais válido). Nesse ponto, a aplicação pode começar tudo novamente, possivelmente com um lock no modo “pessi- mista”, que também é implementado pelo StampedLock. O gerenciamento de tudo isso fica por conta do desenvol- vedor. E como um stamp não pode ser utilizado para des- bloquear outro, deve-se tomar cuidado. Veja a seguir um exemplo deste tipo de lock em ação: 12 InfoQ Brasil Outra novidade do Java 8 lida especificamente com código escalável: os “Adicionadores Concorren- tes (Concurrent Adders)”. Um dos padrões mais bási- cos de concorrência é a leitura e escrita de contadores numéricos. Há diversas maneiras de fazer isso hoje em dia, mas nenhuma tão eficiente ou elegante como a existente no Java 8. Antes do Java 8, isso era feito utilizando “Atomics”, que utilizavam instruções diretas da CPU de compa- ração e swap (CAS), através da classe sun.misc.Un- safe, para realizar a tentativa e marcar o valor de um contador. O problema era que quando um CAS falha- va devido a uma contenção, o AtomicInteger ficava em loop, tentando continuamente o CAS em um loop infinito, até o seu sucesso. Em altos níveis de conten- ção isso poderia se mostrar muito lento. A partir do Java 8 foi adicionado o LongAdders. Esse conjunto de classes fornece uma maneira con- veniente de leitura e escrita de valores numéricos em cenários de grande escalabilidade. A utilização é simples. Basta iniciar um novo LongAdder e utilizar os seus métodos add() e intValue() para aumentar e retornar o contador. A diferença entre esta versão do Atomics e a an- tiga é que, quando o CAS falha devido a alguma contenção, em vez de fazer o giro na CPU, o Adder armazenará o delta em uma célula interna alocada para aquela thread. Então adicionará o valor junto de qualquer outro valor de células pendentes ao resul- tado do método intValue(). Isso reduz a necessidade de voltar e realizar o CAS novamente, ou bloquear outras threads. A utilização do modelo de Adicionadores concor- rentes deve ser sempre preferido frente ao modelo Atômico para o gerenciamento de contadores. 2. Adicionadores concorrentes 3. Ordenação Paralela 4. Migrando para nova API de data Assim como os Adicionadores aceleram os conta- dores, o Java 8 traz uma maneira concisa de acelerar a ordenação. E de uma maneira muito simples, em vez de realizar a ordenação desta maneira: Array.sort(myArray); Agora pode utilizar o método pronto para a para- lelização: Arrays.parallelSort(myArray); Isso automaticamente dividirá a sua coleção em partes, as quais serão ordenadas através de um nú- mero de “cores” e então agrupadas novamente. Há uma ressalva, quando o método de ordenação para- lela é chamado em ambientes com multi-thread ele- vado, tal como um web container sobrecarregado, os benefícios iniciais são reduzidos, até mais de 90%, de- vido ao aumento de trocas de contexto da CPU. O Java 8 introduz uma nova API de datas. Uma nova maneira de lidar com horas e a maioria dos mé- todos da versão atual estão marcados como obsoletos. A nova API traz a facilidade de uso e a precisão for- necidas pela popular API Joda Time para o núcleo da biblioteca Java. Assim como em qualquer nova API, as boas novas são o estilo mais elegante e funcional. Infelizmente, muito já foi feito com a antiga API e a transição deve- rá levar tempo. Para ajudar na transição, a classe Date possui um novo método chamado “toInstant()”, que converte uma data para a nova representação. Isso é especial- mente útil ao fazer uso de APIs que recebe o formato clássico de data, pois basta converter a data para o novo formato e utilizar todos os benefícios da nova API de datas. long stamp = lock.tryOptimisticRead(); //sem caminho bloqueante - muito rápido. work (); // é esperado que não ocorra nenhuma escrita por hora. if (lock.validate(stamp)){ //sucesso! Sem contenção com a thread de escrita. } else { //outra thread precisa obter um bloqueio de escrita entretanto mudando o stamp. //voltando a ter um bloqueio de leitura mais pesado. stamp = lock.readLock(); //Este é uma operação de leitura que causa o lock. try { //Sem escrita acontecendo agora. work(); } finally { lock.unlock(stamp); // Liberação do stamp utilizado. } } 13eMag | Java: Presente e Futuro Lançar um processo do sistema operacional di- retamente do código Java já é possível através das chamadas Java Native Interface (JNI), mas com isso é bem provável se deparar com resultados inesperados ou exceções sérias mais para a frente. Ainda assim, é um mal necessário. Além disso, os processos têm mais um lado desa- gradável – o fato de tenderem a ficar suspensos. O problema ao iniciar um processo a partir do código Java até a versão 7 tem sido difícil o controle sobre um processo uma vez iniciado. Para ajudar nessa tarefa o Java 8 traz três novos métodos na classe “Process”: 1. destroyForcibly() termina um processo com um grau de sucesso bem maior do que antes; 2. isAlive() informa se um processo iniciado ainda está vivo; 3. Uma nova sobrecarga de waitFor() especifica a quantidade de tempo para que o processo termine. Isso retorna ou o sucesso da execução ou o time-out, neste último caso é possível terminar o processo. Duas boas utilizações para estes métodos são: • Caso um processo não termine no tempo deter- minado, forçar seu término e seguir em diante: if (process.wait(MY_TIMEOUT, TimeUnit.MILLISECONDS)){ //success! } else { process.destroyForcibly(); } • Garantir que, antes que um código termine, não seja deixado nenhum processo para trás. Proces- sos suspensos certamente esgotarão seu sistema operacional, mesmo que lentamente. for (Process p : processes) { if (p.isAlive()) { p.destroyForcibly(); } } 5. Controle de processos do sistema operacional 7. Geração segura de números aleatórios 8. Referências opcionais 6. Operações numéricas precisas Um overflow numérico pode causar alguns dos mais estranhos problemas em software, dada sua característica implícita. Isso é especialmente verda- de em sistemas que possuem contadores inteiros in- crementados de acordo com o tempo. Nesses casos, recursos que funcionam corretamente em pré-produ- ção, podem começar a se comportarem de maneira completamente estranha de repente, quando as ope- rações sofrem o overflow e produzem valores ines- perados. Para auxiliar nesse problema, o Java 8 adicionou diversos métodos “exact” à classe Math, protegendo um código suscetível a overflows, através do lança- mento de “uncheckedArithmeticException” quando o valor de uma operação causa um overflow. int safeC = Math.multiplyExact(bigA, bigB); // Será lançada uma ArithmeticException caso o resultado exceda +-2 3^1 O único aspecto negativo disso é que a responsa- bilidade em identificar como o código pode ter pro- blemas com overflow fica porconta de quem está de- senvolvendo. Não há solução mágica, porém isso já é melhor que nada. O Java tem sido criticado por anos pelos proble- mas de segurança. Justo ou não, um grande esforço tem sido feito para blindar a JVM e os frameworks de possíveis ataques. Os números aleatórios com baixo nível de entropia tornam sistemas que os utilizem na criação de chaves para criptografia ou códigos hash mais suscetíveis a ataques. Até o momento, a escolha do algoritmo de geração de números aleatórios é de responsabilidade do de- senvolvedor. O problema é que, nas implementações que dependem de um hardware, sistema operacional ou JVM específicos, o algoritmo desejado pode não estar disponível. Em tais casos, as aplicações têm ten- dência a migrar para versões mais fracas de gerado- res, o que aumenta o risco de ataques. O Java 8 adicionou o novo método SecureRandom. getInstancStrong() com o objetivo de permitir que a JVM escolha uma versão segura. Se escrever código sem o completo controle sobre o hardware/SO/JVM em que será executado, deve-se considerar o uso des- te método. (É uma situação comum quando se cons- troem aplicações a serem executadas na nuvem ou em ambientes de PaaS.) As exceções do tipo NullPointerException são bem antigas. Porém ainda hoje estão presentes no dia-a- -dia de uma equipe de desenvolvimento, não importa quão experiente a equipe seja. Para ajudar com este 14 InfoQ Brasil problema o Java 8 introduz um novo template cha- mado Optional<T>. Baseado em linguagens como Scala e Haskell, esse template indica explicitamente quando uma referên- cia passada ou retornada por uma função pode ser nula. Isso ajuda a reduzir as dúvidas se uma referên- cia pode ou não ser nula, devido à confiança em do- cumentações que podem estar desatualizadas, ou a códigos que podem mudam com o tempo. Optional<User> tryFindUser(int userID) { ou void processUser(User user, Optional<Cart> shoppingCart) { O template Optional possui funções que tornam o trabalho mais conveniente, como “isPresent()” que verifica se um valor não-nulo está disponível, ou “ifPresent()”, que recebe uma função Lambda que será executada caso “isPresent()” seja verdadeiro. O lado negativo é, assim como ocorre com a nova API de Data e Hora, levará tempo até que está novidade esteja presente nas diversas APIs usadas diariamente. Aqui está a nova sintaxe Lambda para escrever um valor opcional: value.ifPresent(System.out::print); Tal Weiss é o CEO da Takipi. Weiss vem desenhando aplicações escalá- veis, de tempo real em Java e C++ nos últimos 15 anos. Ele ainda gosta de analisar um bom bug através da instrumentação de código Java. Em seu tempo livre Weiss toca Jazz na bateria. Sobre o Autor 15eMag | Java: Presente e Futuro ANOTAÇÕES DE TIPOS NO JAVA 8: FERRAMENTAS E OPORTUNIDADES por Todd Schiller, traduzido por Wellington Pinheiro As anotações no Java 8 podem ser escritas não apenas em declarações, mas também em qualquer uso de tipos como nas declarações, generics e conversões de tipos (cast). Nesse artigo são apresentadas as anotações de tipos e as ferramentas que ajudam a construir aplicações melhores. Nas versões anteriores do Java era possível anotar somente declarações. Com o Java 8 as anotações podem ser escritas em qualquer local que os ti- pos são usados, como por exemplo: declarações, generics e conversões de tipos (casts), como apresentado no código a seguir: @Encrypted String data; List<@NonNull String> strings; myGraph = (@Immutable Graph) tmpGraph; À primeira vista, as anotações de tipo não aparentam ser uma das funcionalidades mais atraentes dessa nova versão do Java. Ao contrário, em relação à linguagem em si, as anotações possuem somente uma sintaxe e as fer- ramentas é que dão a sua semântica, isto é, significado e comportamento. Como desenvolvedor Java, é provável que as anotações já estejam sendo utilizadas para melhorar a qualidade do software. Considere a anotação @Override, introduzida no Java 1.5. Em projetos com muitas heranças não triviais, torna-se difícil rastrear qual implementação de um méto- do será executado. Nesse contexto, se não forem tomados os devidos cuidados ao modificar a assinatura de um mé- todo, isso pode fazer com que o método de uma subclasse deixe de ser executado, pois ele deixará de sobrescrever o método da superclasse recém alterado. Eliminar a cha- mada de um método dessa forma pode introduzir uma falha ou alguma vulnerabilidade. Em decorrência disso, a anotação @Override foi introduzida. Ela permite que os desenvolvedores deixem explícito os métodos que so- brescrevem outros métodos da superclasse. O compilador Java usa essa informação para advertir o desenvolvedor quando o código não reflete essa intenção. Usando essa abordagem, as anotações agem como um mecanismo para auxiliar na verificação automática do programa. Além da verificação automática, as anotações têm de- sempenhado um papel central para aumentar a produti- vidade através de técnicas de metaprogramação. A ideia é que as anotações podem informar as ferramentas sobre 16 InfoQ Brasil como gerar código auxiliar, fazer transformações no código ou definir como o programa deverá se com- portar em tempo de execução. Por exemplo a API de persistência JPA (Java Persistence API), introduzida no Java 1.5, permite que os desenvolvedores especifi- quem de forma declarativa uma correspondência en- tre objetos Java e entidades do banco de dados através de anotações, tal como: @Entity. Ferramentas como o Hibernate podem usar essas anotações para fazer ma- peamentos e consultas SQL em tempo de execução. No caso do JPA e do Hibernate, as anotações são usadas para evitar a escrita de código repetitivo, re- duzindo assim a duplicidade de código - princípio co- nhecido como Não Se Repita (Don’t Repeat Yourself - DRY). Curiosamente, sempre que se olha para as fer- ramentas que auxiliam na aplicação de boas práticas, não é difícil encontrar o uso de anotações. Alguns exemplos notáveis ajudam na redução do acoplamen- to através da Injeção de Dependência e também na separação de responsabilidades com a Programação Orientada a Aspectos Mas se as anotações já estão sendo usadas para melhorar a qualidade do código, por que usar anota- ções de tipos? Uma resposta simples é que as anotações de ti- pos trazem mais possibilidades. Elas permitem, por exemplo, que mais tipos de falhas sejam detectados automaticamente e dão mais controle às ferramentas de produtividade. As anotações de tipo no Java 8 podem ser escritas em qualquer local em que um tipo é usado, como por exemplo: @Encrypted String data List<@NonNull String> strings MyGraph = (@Immutable Graph) tmpGraph; Criar uma anotação de tipo é muito simples, basta usar o ElementType.TYPE_PARAMETER, ElementType.TYPE_USE ou ambos, na definição do alvo (target) da anotação. @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE}) public @interface Encrypted { } O ElementType.TYPE_PARAMETER indica que a anotação pode ser usada na declaração de tipos, tais como: classes ou métodos com generics ou ainda jun- to de caracteres coringa em declarações de tipos anô- nimos. O ElementType.TYPE_USE indica que a ano- Sintaxe das anotações de tipo tação pode ser aplicada em qualquer ponto que um tipo é usado, por exemplo em declarações, generics e conversões de tipos (casts). O código a seguir exem- plifica alguns dos usos desses tipos de anotações: @TypeUse class MyClass<@TypeParameter T extends @TypeUse Integer> { @TypeUse <@TypeParameter S> MyClass(String s) { ... } public static void main(String ... args) { Object o = ( @TypeUse String ) new String( “Test” ); if( o instanceof @TypeUse String ){ ... }} } Assim como as anotações em declarações, as anotações de tipos podem ser salvas junto com o arquivo de bytecode gerado e acessadas em tempo de execução via reflection através das políticas de retenção: RetentionPolicy.CLASS ou RetentionPolicy.RUNTIME, usadas na definição da anotação. Existem duas diferenças fundamentais entre as anotações de tipo e as suas predecessoras. Primeiro, as anotações de tipo aplicadas a variáveis locais podem ser salvas junto com o bytecode. Segundo, o tipo usado com generics também é salvo e fica acessível em tempo de execução. Embora as anotações fiquem disponíveis em tem- po de execução, elas não afetam a execução do pro- grama. Por exemplo, um desenvolvedor pode de- clarar duas variáveis do tipo File e uma variável do tipo Connection no corpo de um método, da seguinte forma: File file = new File(“aFile”); @Encrypted File encryptedFile = new File(“anEncryptedFile”); @Open Connection connection = getConnection(); Supondo que a classe Connection tenha um mé- todo send(File), é possível executar esse método pas- sando como parâmetro qualquer uma das duas vari- áveis do tipo File: connection.send(file); connection.send(encryptedFile); Como era de se esperar, a ausência de efeito em tempo de execução implica que, embora os tipos pos- sam ser anotados, os métodos não podem ser sobre- carregados com base nas anotações dos tipos. public class Connection{ 17eMag | Java: Presente e Futuro void send(@Encrypted File file) { ... } // A tentativa de sobrecarga a seguir resulta em um erro de compilação // void send( File file) { ... } . . . } A intuição por trás dessa limitação é que o com- pilador não tem conhecimento sobre a relação entre tipos anotados e não anotados ou entre tipos com di- ferentes anotações. Repare que há uma anotação @Encrypted na va- riável encryptedFile coincidindo com o parâmetro file na assinatura do método send, mas perceba que não há nada nesse método que corresponda à anota- ção @Open na variável connection. Quando é feita a chamada connection.send(...), a variável connection é conhecida como uma receptora (receiver) do método (o termo receiver é uma analogia clássica à troca de mensagens entre objetos na teoria de Orientação a Objetos). O Java 8 introduz uma nova sintaxe para as declarações de métodos de forma que anotações de tipos possam ser escritas em um receptor no método. void send(@Open Connection this, @Encrypted File file) Observe nesse último exemplo a sintaxe para defi- nir o método send. O primeiro parâmetro na verdade faz referência á instância receptora, por isso o nome desse parâmetro é “this”. Como citado anteriormente, as anotações não afe- tam a execução do programa. Assim, um método declarado com a nova sintaxe do parâmetro receptor tem o mesmo comportamento àquele utilizando a sintaxe tradicional. Na prática, o uso da nova sintaxe só permite que anotações de tipos sejam escritas no tipo do receptor. Uma explicação completa da sintaxe de anotações de tipos, incluindo sintaxe para arrays multi-dimen- sionais, pode ser encontrada na especificação JSR (Java Specification Request) 308. Detectando erros com anotações Escrever anotações no código serve para enfatizar erros quando o código tem algum problema: @Closed Connection connection = ...; File file = ...; … connection.send(file); // Problema!: fechada e não criptografada! Contudo, esse último código de exemplo pode ser A anotação @MaybeTainted pode ser vista como um supertipo da anotação @Untainted. Existem vá- rias maneiras de pensar nessa relação. Primeiro, o conjunto de valores que podem ter sido adulterados podem ser um superconjunto de valores que sabida- mente não foram adulterados (um valor que não foi adulterado pode ser um elemento desse superconjun- to). Dessa forma, a anotação @Untainted fornece uma garantia maior que @MaybeTainted. Na prática, essa compilado, executado e causará erro durante a execu- ção. O compilador Java não faz verificações de anota- ções definidas pelo usuário. Apesar disso, a platafor- ma Java expõe duas APIs que auxiliam nessa tarefa, uma para a criação de plugins para o Compilador e outra para o processamento de anotações, de forma que terceiros possam construir seus próprios méto- dos de análise. Nos exemplos anteriores, as anotações tinham a função de qualificar os valores que as variáveis po- deriam conter. Porém, podem ser pensadas outras formas de qualificar o tipo File: @Open, @Localized, @NonNull, etc; também pode ser pensada na aplica- ção dessas anotações qualificando outros tipos, como por exemplo: @Encrypted String. Devido ao fato das anotações serem independentes do sistema de tipos do Java, os conceitos expressos através delas podem ser reutilizadas de muitas formas. Mas como essas anotações poderiam ser automa- ticamente verificadas? Intuitivamente, algumas ano- tações são subtipos de outras anotações e suas apli- cações podem ser verificadas em relação aos tipos. Considere a vulnerabilidade ao ataque de Injeção de SQL, causado pela execução de sentenças SQL modi- ficadas maliciosamente pelo usuário. Pode-se pensar em um tipo de dados classificado como @MaybeTain- ted, indicando que o dado pode ter sido adulterado, ou @Untainted, indicando que o dado está garantida- mente livre de adulteração, pois não foi diretamente informado pelo usuário. @MaybeTainted String userInput; @Untainted String dbQuery; 18 InfoQ Brasil O framework Checker tipagem poderia funcionar da seguinte forma: userInput = dbQuery; // OK dbQuery = “SELECT FROM * WHERE “ + userInput; // erro de tipo! Nesse exemplo, a primeira linha não contém ne- nhum problema, pois userInput supõe que o valor atribuído pode ter sido adulterado, mesmo que esse não seja o caso. Por outro lado, essa tipagem revela um erro na segunda linha, pois está atribuindo um valor que pode ter sido adulterado a uma variável que contém essa restrição. O Checker é um framework que faz verificações de anotações em Java. Lançado em 2007, esse framework é um projeto de código aberto encabeçado pelo sub-li- der da especificação JSR 308, professor Michael Ernst. O Checker vem com um conjunto padrão de anota- ções e verificadores de falhas, tais como: NullPointe- rException, incompatibilidade no uso de unidades de medidas, vulnerabilidades de segurança, problemas de concorrência, entre outros. Por ser um verificador que utiliza mecanismos formais de verificação de ti- pagem baseado em lógica, ele é capaz de reconhecer falhas potenciais que verificadores baseados em heu- rísticas não detectam. O framework usa uma API de compilador que detecta as falhas durante a compila- ção. Outra característica é que ele é estensível, o pró- prio desenvolvedor pode criar rapidamente os seus verificadores de anotações para detectar problemas específicos da aplicação em questão. A meta do Checker é detectar falhas sem que o desenvolvedor tenha que escrever muitas anotações. Isso é feito principalmente através de duas funciona- lidades apelidadas como: padrões mais inteligente e controle de fluxo sensíveis. Por exemplo, durante o processo de verificação de falhas por ponteiros nu- los, o verificador assume que os parâmetros de um método não são nulos por padrão. O verificador pode também utilizar condicionais para determinar quan- do uma atribuição de nulo é uma expressão válida. void nullSafe(Object nonNullByDefault, @Nullable Object mightBeNull){ nonNullByDefault.hashCode(); // OK, devido ao padrão mightBeNull.hashCode(); // Falha! if (mightBeNull != null){ mightBeBull.hashCode(); // OK, devido ao padrão } } Na prática, os padrões e o controle de fluxo sensí- veis significam que o desenvolvedor raramente teráque escrever anotações no corpo dos métodos, pois o verificador é capaz de inferir e verificar as anotações automaticamente. Mantendo a semântica das anota- ções fora do compilador oficial do Java garante que ferramentas de terceiros e os próprios desenvolvedo- res tomem as suas decisões. Isso permite que a veri- ficação de erros possa ser personalizada conforme as necessidades de cada projeto. A habilidade de definir suas próprias anotações também permite que seja considerada a verificação de subtipos específicos do domínio de negócio. Por exemplo, em um sistema financeiro, as taxas de ju- ros usam frequentemente porcentagens enquanto a diferença entre taxas utiliza ponto base (1/100 de 1%). Com um framework de verificação de unidades po- dem ser definidas as anotações @Percent e @Basis- Point para garantir que não há confusão no uso dos dois tipos: BigDecimal pct = returnsPct(...); // anotado para devolver @Percent requiresBps(pct); // erro: requer @BasisPoints No exemplo apresentado, devido ao fato do Che- cker ser sensível ao fluxo de controle, sabe-se que pct é um @Percent BigDecimal quando é feita a cha- mada para requiresBps(pct) com base em dois fatos: returnsPct(...) é anotado para devolver um @Percent BigDecimal e pct não foi novamente atribuído antes de chamar requireBps(...). Frequentemente, desenvol- vedores usam convenções de nomes para tentar pre- venir esses tipos de falha. O que o Checker faz é dar ao desenvolvedor a garantia de que essas falhas não existam, mesmo que o código mude e evolua. O Checker já foi executado em milhões de linhas de código, apontando centenas de falhas, mesmo em código bem testado. Um dos exemplos mais interes- sante aconteceu ao executá-lo com a biblioteca muito utilizada Google Collections, agora Google Guava, que revelou um possível NullPointerException que não tinha sido detectado após vários testes e nem após utilizar ferramentas heurísticas de análise está- tica de falhas. Resultados assim foram obtidos sem fazer muitas alterações no código existente. Na prática, verificar uma propriedade com o Checker requer somente 2 ou 3 anotações para uma centena de linhas de código. Para aqueles que utilizam o Java 6 ou 7, também é possível usar o Checker para melhorar a qualidade do código. As anotações podem ser escritas em co- mentários, tal como: 19eMag | Java: Presente e Futuro /* @NonNull */ String aString Historicamente, a razão disso é que o Checker foi desenvolvido junto com a JSR 308, que teve início em 2006. As anotações são especificações declarativas para informar como as ferramentas devem gerar código ou arquivos auxiliares e como as ferramentas devem se comportar durante a execução do programa. O uso das anotações desse modo pode ser considerado uma forma de metaprogramação. Alguns frameworks, tal como o Lombok, tiram vantagem da metaprograma- ção com anotações ao extremo, resultando em um có- digo que mal parece Java. Considere a Programação Orientada a Aspectos (Aspect Oriented Programming - AOP). A AOP aju- da na separação de propriedades ortogonais de uma aplicação, tais como log e autenticação, daquelas rela- cionadas às regras de negócio. Com a AOP é possível Apesar do Checker ser o melhor framework para fazer verificação de falhas utilizando anotações, ele não é o único atualmente. Tanto o Eclipse quanto o IntelliJ dão suporte a esse tipo de anotação: executar um aplicativo em tempo de compilação que adiciona código ao programa com base em um con- junto de regras. Por exemplo, pode ser definida uma regra que automaticamente faz autenticação com base no tipo anotado: void showSecrets(@Authenticated User user){ // Inserido automaticamente pela AOP: if (!AuthHelper.EnsureAuth(user)) throw . . .; } Como antes, a anotação está qualificando o tipo. Ao invés de verificar as anotações em tempo de com- pilação, a AOP é usada para fazer a verificação em tempo de execução automaticamente. Esse último POSSUEM SUPORTE Checker Framework Suporte completo, incluindo anota- ções em comentários. Eclipse Suporte à verificação de valores não nulos. IntelliJ IDEA Podem ser escritos inspecionadores personalizados, suporte à verificação de valores não nulos. NÃO POSSUEM SUPORTE PMD Coverity Check Style Sem suporte para Java 8. Find Bugs Sem suporte para Java 8. Aumentando a produtividade com anotações de tipo A ideia inicial por trás das anotações de tipo era a verificação de falhas. Contudo, ainda haviam muitas apli- cações convincentes desse tipo de anotação em ferramentas de produtividade. Para entender um pouco melhor o porquê, considere os seguintes exemplos de como as anotações são usadas Programação Orientada a Aspectos @Aspect, @Pointcut, etc. Injeção de Dependência @Autowired, @Inject, etc. Persistência @Entity, @Id, etc. 20 InfoQ Brasil exemplo mostra a anotação de tipo sendo usada para dar um controle maior sobre como e quando a AOP modifica o programa. O Java 8 também dá suporte às anotações de tipo em declarações locais que são persistidas nos arqui- vos de bytecode. Isso cria novas oportunidades para se ter uma AOP mais granular. Por exemplo, para incluir código de rastreamento de uma forma mais granular: // Faz o rastreamento de todas as chamadas ao objeto ar @Trace AuthorizationRequest ar = . . .; Novamente, as anotações de tipo dão maior con- trole ao se fazer metraprogramação com AOP. A In- jeção de Dependência é uma história similar. Com o Spring 4, é finalmente possível usar generics como qualificador: @Autowired private Store<Product> s1; @Autowired private Store<Service> s2; Com generics é possível eliminar a necessidade introduzir classes como ProductStore e ServiceStore ou usar regras mais frágeis de injeção baseada em nomes. Com anotações de tipo, não é difícil imaginar que um dia o Spring permita usá-las para controlar a inje- ção da seguinte forma: @Autowired private Store<@Grocery Product> s3; Esse exemplo mostra um tipo anotado servindo como um mecanismo de separação de interesses, fa- vorecendo para que a hierarquia de tipos do projeto seja mais concisa. Essa separação só é possível por- que as anotações de tipo são independentes do siste- ma de tipagem do Java. Um bom exemplo dessa abordagem é o framewok EnerJ, de Adrian Sampson, para computação com eficiência energética utilizando a computação aproxi- mada. EnerJ baseia-se na observação de que em certas ocasiões, tal como o processamento de imagens em dispositivos móveis, faz sentido reduzir a precisão para poupar energia. Um desenvolvedor usando o EnerJ pode anotar um dado que não é crítico usan- do a anotação @Approx. Com base nessa anotação, o ambiente de execução do EnerJ usa vários atalhos ao manipular esse dado. Por exemplo, pode ser utiliza- do um hardware de cálculos aproximados de baixo consumo de energia para armazenar e fazer cálculos. Contudo, tendo dados aproximados se movendo pelo programa pode ser perigoso, e nenhum desenvolve- dor vai querer controlar o fluxo afetado por esse dado aproximado. Portanto, o EnerJ usa o Checker para ga- rantir que os dados aproximados não sejam utiliza- dos em sentenças de controle de fluxo, por exemplo, em sentenças condicionais como ifs. Mas as aplicações dessa abordagem não são limi- tadas a dispositivos móveis. Em finanças, frequen- temente há um conflito entre precisão e velocidade. Nesses casos, o ambiente de execução pode decidir entre usar um algoritmo de Monte Carlo para o cálcu- lo de caminhos, um critério de convergência ou mes- mo processar algo em um hardware especializado com base nas demandas atuais ou na disponibilidade de recursos. A beleza desse abordagem é que a preocupação de como uma execução deve ser feita fica fora da lógica de negócio central da aplicação,que descreve o que deve ser feito. Como apresentado, as anotações de tipo podem ser usadas tanto para detectar como prevenir erros em programas e também para aumentar a produti- vidade. Contudo, o potencial real das anotações de tipos está em combinar a verificação de erros e a me- taprogramação para dar suporte aos novos paradig- mas de desenvolvimento. A ideia básica é construir ambientes de execução e bibliotecas que alavanquem a abordagem de criar programas automaticamente mais eficientes, parale- los ou seguros e que façam com que os desenvolvedo- res usem as anotações corretamente. A estrada à frente 21eMag | Java: Presente e Futuro Todd Schiller é diretor da FinLingua, uma companhia de consultoria e desenvolvimento de software para a área financeira. A prática de consultoria da FinLingua ajuda equipes de desenvolvimento a adotar uma linguagem específica de domínio, metraprogramação e técnicas de análise de programas. Todd é um membro ativo da comunidade de pesquisa em engenharia de software; sua pesquisa em especificação e verificação tem sido apresentada nas principais conferências interna- cionais, incluindo ICSE e OOPSLA. Sobre o Autor No Java 8, as anotações podem ser usadas em qualquer tipo, incrementando a capacidade de escrever anota- ções em declarações. As anotações por sí próprias não afetam o comportamento do programa. Contudo, utili- zando ferramentas como o Checker, é possível utilizá-las para verificar automaticamente a ausência de falhas e aumentar a produtividade com metaprogramação. Enquanto ainda levará algum tempo para que as ferramentas existentes obtenham total vantagem das anotações de tipos, agora é hora de começar a explorar como as anota- ções de tipos podem melhorar tanto a qualidade do software desenvolvido quanto a produtividade. Conclusão 22 InfoQ Brasil JAVA 8: DESMISTIFICANDO LAMBDAS por Simon Ritter, traduzido por Rafael Sakurai O artigo destaca os pontos principais da palestra “Lambdas & Streams” que Simon Ritter apresentou no QCon London 2014. Ele explica o que são as expressões Lambdas, interface funcional, referência de métodos, Streams, operações de agregação e diversos exemplos Se perguntarmos aos desenvolvedores Java sobre o Java 8, teremos diversas respostas bem animadas, especialmente sobre o uso das expressões lambda. Mas após uma conversa mais honesta, encontramos um fervor misturado com um pouco de receio em relação às novas e misteriosas APIs disponíveis na Web. Simon Ritter revelou alguns dos mistérios na apresentação sobre lambdas na conferência QCon London 2014. A seguir, temos um trecho de código Java que ilustra um padrão comum: Nota: As partes em vermelho são aquelas que estamos interessados; e as partes em azul representam código re- petitivo. Neste problema, queremos encontrar a maior nota em uma coleção de estudantes. Usamos um idioma comum de iteração externa para percorrer e comparar cada ele- mento da coleção. Mas há algumas desvantagens no código. Em primeiro lugar, a iteração externa significa que os desenvolvedores são responsáveis pela implementação (programação im- perativa), e como é usado um único loop, definimos que a execução do código será de modo sequencial. Se qui- sermos otimizá-lo, não poderíamos facilmente segmentar em um conjunto de execução de instruções em paralelo. Em segundo lugar, a variável highestScore é mutável e não é thread-safe. Então, mesmo que quiséssemos que- brá-lo em múltiplas threads, precisaríamos adicionar um lock (bloqueio) para prevenir race conditions (condições de concorrência), que por sua vez podem introduzir pro- blemas de desempenho. Agora, se quisermos agir de modo mais inteligente, podemos mudar um pouco mais a implementação em direção ao estilo funcional utilizando uma classe interna anônima: 23eMag | Java: Presente e Futuro Nessa implementação, eliminamos o estado mu- tável e passamos o trabalho da interação para a bi- blioteca. Estamos encadeando uma sequência de cha- madas de métodos para aplicar a operação em uma expressão “Olhe para todos os meus estudantes e fil- tre apenas aqueles que se graduaram em 2011”. A classe interna anônima implementa a interface Predicate (ela contém um método, aceita um parâme- tro e retorna um valor booleano) com o método cha- Pense nas expressões lambda como um método, no sentido de que ele aceita parâmetros, tem corpo e retorna um tipo estático. Nesse exemplo, usamos as expressões lambda para obter o mesmo algoritmo que determina qual a maior nota, como nos exemplos anteriores. Vejamos em detalhes. Primeiro, criamos um Stream a partir de uma Col- lection. O método stream é novo na interface Collec- tion e funciona de forma parecida com um Iterator embutido (veremos em mais detalhes logo adiante). O stream prepara os resultados da coleção, que en- tão passa para o método filter, descrevendo “como” as partes serão filtradas usando a expressão lambda que compara o ano de graduação dos estudantes com mado “op”, que simplesmente compara o ano de gra- duação do estudante com 2011 e retorna o resultado. Enviaremos o resultado (todos os estudantes gra- duados em 2011) para o método “map”, que usará ou- tra classe interna anônima para chamar um método da interface de mapeamento com seu único método “extract” para extrair o dado que queremos (chaman- do o método getScore). Então passaremos esse resul- tado, que é um conjunto de notas de todos estudantes graduados em 2011, para o método “max”, que enfim entregará o maior valor a partir do conjunto de re- sultados. Usando essa abordagem, tratamos toda a intera- ção, filtro e acúmulo com o uso da biblioteca, sem precisar fazer isso de forma explícita. Isso não somen- te simplifica a implementação, como também elimina o compartilhamento de estado, deixando mais fácil pedir ao código da biblioteca para decompor a imple- mentação em diversas sub tarefas e alocá-lo em di- ferentes threads para serem executadas em paralelo. Em muitos casos, também executamos uma avaliação posterior, economizando ainda mais tempo. Então, uma abordagem que usa classe interna anônima é rápida e thread-safe, mas atente-se para as cores do código fonte. Note que a quantidade em azul é maior do que em vermelho, indicando código repetitivo. Então, entram em cena as expressões lambda! 2011. Note que não há um retorno explicito. Simples- mente dizemos “Compare o ano de graduação com 2011” e o compilador deduz que destina-se a interface Predicate (que tem um único método que necessita de uma assinatura com um retorno do tipo booleano). O método map é processado de forma similar, usan- do uma expressão lambda, passando um estudante S como parâmetro e mapeando (como um tradutor) seu valor de retorno, que é a nota (score) do estudante. O método map não deve ser confundido com o java.util. Map que usa pares de chave-valor. O método map da classe Stream retorna uma nova instância do Stream contendo os resultados da operação aplicada para to- dos os elementos da Stream de entrada, produzindo, 24 InfoQ Brasil nesse caso, um Stream com todas as notas. Usando lambdas implementamos o mesmo algo- ritmo com muito menos código. É mais compreensí- vel, portanto menos propenso a erros, e como visto, ele pode ser alterado para um algoritmo paralelo uma vez que não há compartilhamento de estado. Como Ritter disse em sua apresentação: Ritter ampliou esse conceito apontando que uma vez que uma lambda é uma função sem uma classe, então a palavra-chave “this” não se refere a própria lambda, mas sim a classe na qual foi declarada. Isso distingue de uma classe interna anônima, na qual “this” refere-se a própria classe interna. É útil olhar para as decisões de implementações que os designers da linguagem fizeram para desen- volver os lambdas.Olhando para o Java como um todo, há muitas in- terfaces que possuem apenas um método. Vamos definir uma interface funcional como uma interface com exatamente um método abstrato, por exemplo: interface Comparator<T> { boolean compare(T x, T y); } interface FileFilter { boolean accept(File x); } interface Runnable { void run(); } interface ActionListener { void actionPerformed(…); } interface Callable<T> { T call(); } Uma expressão Lambda permite definir uma inter- face funcional (novamente, um método abstrato) que A sintaxe da referência de método é outra nova funcionalidade da expressão lambda. É um atalho que permite reutilizar um método basicamente como uma expressão Lambda. Podemos fazer coisas como: FileFilter x = f -> f.canRead(); Essa sintaxe diz para o programa criar um FileFil- ter que filtra os arquivos com base em uma proprie- dade comum – nesse caso, se o arquivo pode ser lido. Note que nesse exemplo, nunca mencionamos que f é um arquivo; o compilador infere através da assinatu- ra do único método no FileFilter: boolean accept(File pathname); Podendo ser simplificado ainda mais usando a nova notação do Java 8 “::”. FileFilter x = File::canRead; o compilador identifica pela estrutura. O compilador pode determinar a interface funcional representada a partir de sua posição. O tipo de uma expressão lamb- da é o da interface funcional associada. Como o compilador conhece os locais que são usa- dos uma expressão lambda, é possível determinar muito sobre essa expressão. Portanto como o compi- lador sabe o tipo da interface funcional, então pode inferir os outros tipos necessários. “As expressões Lambda representam uma função anônima. Então, elas são como métodos, mas não são realmente um método. Elas são como funções anô- nimas no sentido de que elas tem as mesmas funcio- nalidades de um m étodo, mas elas não são métodos porque não estão associadas com uma classe. Se pen- sar no Java como programamos hoje, criamos classes e classes têm métodos. Portanto o método tem uma classe associada a ele. No caso das expressões Lambd dentro de uma expressão Lambda e ela tem um corpo, que defini o que ela fará. Também é possível fazer as mesmas coisas que um método: pode agrupar instru- ções; pode usar chaves e ter múltiplas instruções sem nenhum problema. A coisa mais importante sobre isso é que agora permite usar uma maneira simples de ter um procedimento parametrizado, não apenas valores parametrizados.” Mas, Ritter avisa: “Um ponto de atenção é que embora não tenhamos explicitamente colocado o tipo da informação lá, isso não significa que está usando uma tipagem dinâmica no Java. Nunca faríamos isso, é desagradável e ruim. Então, o que fazemos é dizer que isso ainda é uma tipa- gem muito estática, mas com menos digitação.” Ainda segundo Ritter, uma coisa que diferencia as expressões lambda dos closures, é que ao contrário dos closures, as lambdas não podem acessar as vari- áveis que estão fora da expressão lambda, exceto as variáveis que são efetivamente final, isso significa que embora a variável não precisa da palavra-chave final (ao contrário de uma classe interna), no entanto o seu valor não pode ser reatribuído.” Referência de métodos 25eMag | Java: Presente e Futuro Essas sintaxes são completamente equivalentes. Para chamar um construtor de forma análoga, pode ser utilizado a sintaxe “::new”. Por exemplo, se tivermos uma interface funcional como: interface Factory<T> { T make(); } Então, podemos dizer: Factory<List<String>> f = ArrayList<String>::new; Isso é equivalente a: Factory<List<String>> f = () -> return new ArrayList<String>(); E agora, quando f.make() é chamada, será retorna- do um novo ArrayList<String>. Usando as interfaces funcionais, o compilador pode deduzir muitas coisas sobre a tipagem e inten- ção, como demonstrado nesses exemplos. Operações de negócios frequentemente envolvem agregações como: encontrar a soma, máximo, ou mé- dia de um conjunto de dados, ou grupo de alguma coisa. Até agora, tais operações são normalmente exe- cutadas com loops de interação externa, como disse- mos, nos restringindo das otimizações e adicionando boilerplate ao código fonte. As Streams do Java SE 8 tem como objetivo resol- ver estes problemas. Nas palavras de Ritter: Uma das vantagens das lambdas e expressão de códigos como dados é que, como visto, as bibliotecas existentes foram atualizadas para aceitar lambdas como parâmetros. Isso introduz alguma complexida- de: como introduzir métodos na interface sem que- brar as implementações das interfaces que já existem? Para fazer isso, o Java introduz o conceito de mé- todos de extensão, também conhecido como defender métodos ou métodos default (padrão). Vamos explicar usando um exemplo. O método stream foi adicionado na interface Collection para fornecer um suporte básico ao lambda. Para adicio- nar o método stream na interface sem quebrar as im- plementações existentes da Collection de todo mun- do, o Java adicionou o stream como um método da interface, fornecendo uma implementação padrão: interface Collection<E> { default Stream<E> stream() { return StreamSupport.stream(spliterator()); } } Então, agora temos a opção de implementar o mé- todo stream ou se preferir usar a implementação pa- drão fornecida pelo Java. A resposta é que potencialmente não será finaliza- do. É possível fazer facilmente um trecho de código usando streams que continua para sempre, como se fosse um loop “while(true);” infinito. Ele é como um Stream: se usar um Stream infinito, ele pode nunca terminar. Mas também é possível fazer um Stream parar - digamos, para fornecer um Stream infinito de números aleatórios, mas com um ponto de parada. Assim o Stream vai parar e o programa pode conti- nuar sua execução. O Stream fornece um pipeline (sequência) de da- dos com três componentes importantes: 1. Uma fonte de dados; 2. Zero ou mais operações intermediarias, forne- cendo um pipeline de Streams; 3. Uma operação terminal(final) que pode realizar duas funções: criar um resultado ou um efeito co- lateral. (Um efeito colateral significa que talvez não consiga retornar um resultado, mas ao invés disso, consiga imprimir o valor.) Evolução da biblioteca Operações de agregação “Um stream é a maneira de abstrair e especificar como processar uma agregação. Ele não é uma estru- tura de dados. Na realidade é uma maneira de tratar os dados, mas define uma nova estrutura de dados, e in- teressantemente pode tanto finito quanto infinito. En- tão, é possível criar um stream de, digamos, números aleatórios e não precisa mais ter um limite. É aqui que, algumas vezes, as coisas ficam um pouco confusas. Re- flita sobre a seguinte questão: - Se tenho um stream infinito, posso continuar processando os dados para sempre. Como faço para parar o que estou fazendo com os dados?” 26 InfoQ Brasil Nesse exemplo, iniciamos com uma Collection de “transações” e queremos determinar o preço total de todas as transações que foram realizadas por com- pradores de Londres. Obtemos um stream a partir da Collection de transações. Depois obtemos um stream a partir da Collection de transações. Então, aplicamos uma operação de fil- tro para produzir um novo Stream com os comprado- res de Londres. A seguir, aplicamos a operação intermediaria mapToInt para extrair os preços. E finalmente, aplica- mos a operação final de soma para obter o resultado. Do ponto de vista da execução, o que aconteceu aqui é que o filtro e o método de map (a operação intermediaria) não realizaram um trabalho compu- tacional. Foram apenas responsáveis por configurar o conjuntodas operações, e a computação real é exe- cutada posteriormente, postergado até chamar a ope- ração final – nesse caso a soma (sum) – que faz todo trabalho acontecer. Depois de encadearmos todos esses streams, po- demos especificar uma operação de finalização para executar as pipeline e todas as operações (sequencial- mente ou em paralelo) e produzir os resultados finais (ou efeitos colaterais). int sum = transactions.stream(). filter(t -> t.getBuyer().getCity().equals(“London”)). //Lazy mapToInt(Transaction::getPrice). //Lazy sum(); //Executa o pipeline Essa é uma velha conhecida dos dias do Java 1.5, ex- ceto que agora tem um método forEach() que aceita um Consumer, que aceita um simples argumento e não retorna valor e produz um efeito colateral. Mas continua sendo uma interação externa e a melhor for- ma de obter um lambda é o método map(). Exemplos: Ritter conclui a apresentação com alguns exemplos uteis, que estão listados a seguir com comentários ex- plicativos. (As linhas em negrito indicam o especial uso demonstrado em cada exemplo). Há diversas formas de obter um Stream. Muitos métodos estão sendo adicionados a API de Collection (usando a extensão de métodos nas interfaces). Através de uma List, Set, ou Map.Entry é possível chamar um método de Stream que retorna uma Stre- am com o conteúdo da coleção. Um exemplo é o método stream(), ou parallelStre- am(), que internamente a biblioteca usa o framework de fork/join para decompor as ações em diversas sub- tarefas. Há outras maneiras de obter um stream: • Fornecer um array para o método stream() da classe Arrays; • Chamar o método Stream.of(), um método está- tico da classe Stream; • Chamar um dos novos métodos estáticos para retornar um stream em particular, por exemplo: » IntStream.range(), fornecendo um índice de inicio e fim. Por exemplo, IntStream.range(1, 10) gera um stream de 1 a 9 com incremento de 1 em 1 (IntRange.rangeClosed(1, 10) gera um stre- am de 1 a 10); » Files.walk() passando um caminho e algum parâmetro de controle opcional que retorna um stream de arquivos individuais ou subdiretó- rios; » Implementar a interface java.util.Spliterator para definir sua própria maneira de criar um Stream. Para mais informações sobre o Splite- rator consulte o Javadoc do SE 8 fornecido pela Oracle. Fontes de stream Operações de finalização de Stream Inteface Iterable 27eMag | Java: Presente e Futuro Exemplo 1. Converter palavras para maiúsculo: List<String> output = wordList.stream(). // Mapa de toda a lista de String em maiúsculo. map(String::toUpperCase). // Converte o stream para uma lista. collect(Collectors.toList()); Exemplo 2. Procurar as palavras com tamanho par na lista: List<String> output = wordList. stream(). //Seleciona somente as palavras com tamanho par. filter(w -> (w.length() & 1 == 0). collect(Collectors.toList()); Exemplo 3. Contar as linhas de um arquivo: long count = bufferedReader. // Recebe um stream com linhas individuais. Esse é o novo método do // bufferedReader que retorna um stream<string>. lines(). // Conta os elementos do stream de entrada. count(); Exemplo 4. Juntar as linhas 3 e 4 em uma única String: String output = bufferedReader. lines(). // Pula as duas primeiras linhas. skip(2). // limita a stream a apenas as próximas duas linhas. limit(2). // Concatena as linhas. collect(Collectors.joining()); Exemplo 5. Encotrar o tamanho da linha mais longa em um arquivo: int longest = reader.lines(). mapToInt(String::length). // cria um novo stream com o tamanho das strings mapeando // a atual String ao tamanho correspondente. max(). // Coleta o maior elemento do stream de tamanho (como uma optionalInt) getAsInt(); // Atualiza o OptionalInt com um int. Exemplo 6. Coleção de todas as palavras do arquivo em uma lista: List<String> output = reader. lines(). flatMap(line -> Stream.of(line.split(REGEXP))). // Recebe um stream de palavras de // todas as linhas. filter(word -> word.length() > 0). // Filtra as Strings vazias. collect(Collectors.toList()); // Cria a lista de retorno. Exemplo 7. Retorna a lista de palavras minúscula em ordem alfabética: List<String> output = reader.lines(). flatMap(line -> Stream.of(line.split(REGEXP))). filter(word -> word.length() > 0). map(String::toLowerCase). // Atualiza o Stream da fonte com o Stream de // letras minúsculas. sorted(). // Atualiza o stream com a versão ordenada. collect(Collectors.toList()); // Cria e retorna uma Lista 28 InfoQ Brasil Simon Ritter é o diretor do Evangelismo da tecnologia Java na Oracle Corporation. Ritter trabalha com negócios de TI desde 1984 e é bacharel de Ciência graduado em Física pela universidade de Brunel no Reino Unido. Sobre o Autor Simon Ritter conclui a apresentação declarando: O Java 8 está disponível para download e há um bom suporte a lambda em todos as principais IDEs. Sugiro que todos os desenvolvedores Java façam o download e usem o Projeto Lambda. Conclusão “O Java precisa das expressões lambda para facilitar a vida do desenvolvedor. As expressões lambdas eram ne- cessárias para a criação dos Streams e também para implementar a ideia de passagem de comportamento como a passagem de valor. Também precisávamos ampliar as interfaces existentes, com o uso das extensões de métodos do Java SE 8, e que resolve o problema da retro compatibilidade. Isso permite fornecer a ideia de operações em lote na Collections e permite fazer coisas que são mais simples, e de um modo mais legível. O Java SE 8 está basicamente evoluindo a linguagem; evoluindo as bibliotecas de classes e também as maquinas virtuais ao mesmo tempo.” 29eMag | Java: Presente e Futuro QUÃO FUNCIONAL É O JAVA 8? por Ben Evans, traduzido por Roberto Pepato Tem sido falado que o Java 8 trouxe a Programação Funcional para o Java. Neste artigo, Ben Evans discute o que significa ser funcional. Olhando a evolução do Java — em particular o seu sistema de tipos, é possível ver como os novos recursos do Java 8, especialmente as expressões lambda, mudam o panorama e fornecem alguns benefícios fundamentais para o estilo de programação funcional. Tem-se falado muito sobre como “o Java 8 trouxe a Programação Funcional para o Java” - mas, o que isso realmente quer dizer? Neste artigo, será apresentado o que significa ser funcional para uma linguagem, ou para um estilo de programação. Olhando a evolução de Java, em particular o seu sistema de tipos (type system), é possível ver como os novos recursos do Java 8, especialmente as expressões lamb- da, mudam este cenário e oferecem alguns dos principais benefícios da programação funcional. ritmos como estruturas mais funda- mentais que os dados que operam. Algumas destas linguagens buscam desmembrar o estado do programa de suas funções (de uma forma que pare- ce contrária ao desejo das linguagens orientadas a objetos, que normalmen- te buscam manter algoritmos e dados integrados). Um exemplo seria a linguagem de programação Clojure. Apesar de exe- cutar sobre a Java Virtual Machine, que é baseada em classes, a Clojure é fundamentalmente uma linguagem funcional e não expõe diretamente as Em sua essência, uma linguagem de programação funcional é aquela que trata da mesma forma tanto o có- digo como os dados. Isto significa que uma função deve ser um valor de pri- meira classe na linguagem e deve po- der ser atribuída a variáveis, passada como parâmetro para funções, entre outras funcionalidades. De fato, muitas linguagens funcio- nais vão ainda mais longe que isso e enxergam a computação e os algo- O que é uma linguagem de programação funcional? 30 InfoQ Brasil
Compartilhar