São muitas as novidades e eu vou compartilhar aqui um resumo das principais delas, além de passar vários links e recursos que você pode utilizar para se atualizar.
Um java interativo
Ao baixar e configurar o Java 9 em seu ambiente você já consegue utilizar uma das grandes introduções da versão, que é a nova ferramenta REPL (Read-Eval-Print Loop).
Ela traz uma forma muito mais interessante de experimentar código, sem toda burocracia e verbosidade das classes com método main. Basta abrir o console, digitar jshell
— que é o nome da ferramenta — e sair escrevendo código Java!
turini ~ $ jshell
| Welcome to JShell — Version 9
| For an introduction type: /help intro
|
Eu li a documentação completa da nova especificação da linguagem com o JShell aberto, experimentando cada uma das mudanças e com zero preocupações de setup. Foi uma experiência incrível.
E é muito legal também saber que você pode escrever algum snippet de código nele e depois compartilhar, pra que outras pessoas possam facilmente testar sua implementação — ou mesmo modificá-la e te enviar de volta.
Quer um exemplo? Podemos criar um método com o mesmo código usado no post de Java 8, para retornar a média de tamanho de uma lista de palavras.
Vou abrir o JShell e escrever o código em um método chamado averageLenght
:
jshell> public double averageLenght(List<String> words) {
…> return words.stream()
…> .mapToInt(String::length).average()
…> .orElse( 0 );
…> }
| created method averageLenght(List<String>)
|
Agora criar uma lista de palavras:
jshell> List<String> words = List.of(“podemos”, “criar”, “listas”, “assim”);
|
E experimentar minha implementação:
jshell> averageLenght(words)
==> 5.2
|
Funciona!
Para salvar esse código e compartilhar, vou usar o comando /save
.
jshell> /save average-lenght
|
O arquivo
average-lenght foi criado. Experimente agora
baixar esse arquivo e depois abrir o seu jshell com ele como a seguir:
turini ~ $ jshell average-lenght
|
A lista de palavras e o método averageLenght
já vão estar aí, disponíveis para você testar.
Imagina que legal no fórum da Alura ou GUJ, por exemplo, você conseguir enviar o pedacinho de código problemático para que o pessoal abra e te ajude encontrar o problema? Você consegue criar classes, métodos e inclusive usar dependências externas — JARs de bibliotecas etc — nesse ambiente interativo, e tudo que a pessoa precisa é ter o Java 9 instalado.
Um Java reativo
O JDK 9 também evoluiu a arquitetura do pacote
java.util.concurrent
, com a introdução da
Flow API que implementa a
especificação de Reactive Streams. A API é composta pela classe abstrata
Flow
e suas interfaces internas, que seguem o padrão de publicador e assinante —
publisher/subscriber pattern.
java.util.concurrent.Flow
java.util.concurrent.Flow.Publisher
java.util.concurrent.Flow.Subscriber
java.util.concurrent.Flow.Processor
|
Apesar do nome extremamente familiar,
Reactive Streams não tem relação direta com a API de Streams do Java 8. Essa é uma confusão completamente comum, mas nesse caso o termo Streams remete a fluxo, fluxos reativos. Um passo criado a partir do caminho definido pelo
famoso manifesto reativo.
Ele vem pra resolver o famoso problema de processamentos assíncronos em que um componente de origem envia dados sem saber ao certo se isso está em uma quantidade maior do que aquela com a qual o consumidor pode lidar.
Perceba que na imagem o componente de origem está recebendo e processando várias informações, mas muitas delas estão bloqueadas enquanto as outras estão sendo processadas. Os dados chegam em uma velocidade muito maior do que podem ser atendidos. Com a Flow API você consegue resolver esse e outros problemas avançados em execuções assíncronas com back pressure.
O fluxo seria assim:
No lugar de a origem empurrar uma quantidade arbitrária de dados para o destino, é o destino que puxa apenas a quantidade de dados que ele certamente poderá atender. Só então, os dados são enviados e na quantidade certa.
No exemplo a seguir estamos criando um publicador e registrando implicitamente um consumidor que apenas imprime as String
s conforme elas são enviadas:
jshell> SubmissionPublisher<String> publisher = new SubmissionPublisher<>();
jshell> publisher.consume(System.out::println);
jshell> List.of( "esse" , "código" , "é" , "assíncrono" ).forEach(publisher::submit);
esse
código
é
assíncrono
|
Um Java modular
Depois de mais de 20 anos muitos dos problemas da antiga estrutura monolítica da plataforma Java foram resolvidos com a introdução de um novo sistema de módulos e a modularização do próprio JDK. Neste
outro post eu mostro o Jigsaw e o que muda na estrutura de seus projetos, além de mencionar brevemente como ficou a divisão de suas principais APIs.
No Java 9 tudo é modular, mas você não precisa sair migrando os seus projetos para usar essa nova versão. Especialmente por motivos de compatibilidade, projetos que não definem explicitamente o uso do sistema de módulos vão funcionar normalmente — internamente é declarado um unamed module
, com acesso a todos os módulos da plataforma de forma parecida ao que acontece nas versões anteriores.
Apesar disso, depois de conhecer um pouco mais sobre os benefícios dessa migração eu arriscaria dizer que você vai querer migrar. Uma das grandes vantagens dessa nova estrutura é que, diferente da abordagem atual do classpath, as informações do módulo precisam ficar disponíveis da mesma forma durante as fases de compilação e execução. Isso garante uma integridade muito maior nos projetos, evitando problemas da abordagem atual do legado classpath — como o famoso JAR hell — ou, pelo menos, reportando-os muito antes, em tempo de compilação.
Outro ganho enorme é no encapsulamento dos projetos, já que agora ser público não significa mais ser acessível. Em um sistema modular você precisa definir explicitamente o que pode ou não ser acessado por fora do módulo, e isso guia a construção de APIs com designs mais lógicos.
No exemplo a seguir o arquivo module-info.java
, responsável pela definição de um módulo, exemplifica um caso em que o módulo br.com.caelum.desktop
precisa do módulo base do Java FX para funcionar, e deixar acessível apenas seu pacote de componentes:
module br.com.caelum.desktop {
requires javafx.base;
exports br.com.caelum.desktop.components;
}
|
Ah, e em projetos modulares você consegue criar imagens de execução customizadas com apenas o pedacinho da JRE que você está usando! Podemos falar que essa é uma extensão aos
compact profiles do JDK 8, mas que funcionam de forma muito mais interessante já que você literalmente só carrega o que precisa. Muitos projetos pequenos podem ter só o
java.base
, no lugar de todos os 94 módulos e suas milhares de classes. Isso tem uma série de vantagens de performance e consumo de memória, já que a aplicação fica com um
footprint inicial muito menor.
Novas APIs
A versão também recebeu diversas novas APIs. Um destaque especial vai para a de HTTP/2 Client, que possui uma interface publica completamente repaginada ante ao legado HttpURLConnection API e com suporte para requisições HTTP/2 e WebSockets.
Um exemplo de código para retornar o conteúdo do corpo de um request seria:
String contentBody = newHttpClient().send(
newBuilder()
.uri( new URI(“https:
.GET()
.build(), asString())
.body();
|
O mais interessante dessa API é que ela é o piloto de um novo conceito da linguagem, que são os módulos em incubação. A ideia é permitir que APIs passem por um período de testes antes de entrar definitivamente para a linguagem, com objetivo de reduzir a possibilidade de introdução de erros na plataforma, que tem o forte peso da retrocompatibilidade.
Módulos em incubação ainda não fazem parte do Java SE. Eles ficam em um pacote jdk.incubator
e não são resolvidos por padrão na compilação ou execução de suas aplicações. Para testar esse meu exemplo no jshell, por exemplo, você precisa iniciar a ferramenta com essa opção explicitamente:
turini ~ $ jshell --add-modules jdk.incubator.httpclient
| Welcome to JShell — Version 9
| For an introduction type: /help intro
|
O JDK 9 também traz uma nova API de logging, que te possibilita criar um provedor padrão de mensagens que poderá ser usado tanto em seu código como no do próprio JDK. E uma API de Stack-Walking, que tira proveito dos poderosos recursos da API de Streams para que você consiga passear pela Stack de sua aplicação de forma extremamente eficiente. Com ela podemos facilmente coletar em uma lista todas as classes de um pacote específico, ou alguma outra condição especial de filtro:
StackWalker.getInstance().walk(stream ->
stream.filter(frame -> frame.getClassName().contains( "br.com.caelum" )
))
.collect(toList());
|
Eu consigo ver um futuro próximo em que as IDEs e ferramentas de profilling tiram bastante proveito dessa API para fornecer alternativas performáticas e poderosas para analise e investigação de problemas de runtime.
Diversas mudanças nas APIs
Eu mostrei
nesse post que as Collections receberam diversos
factory methods trazendo uma forma mais próxima aos
collection literals para criação de listas, sets e mapas.
Map.of( 1 , "Turini" , 2 , "Paulo" , 3 , "Guilherme" );
List.of( 1 , 2 , 3 );
Set.of( "SP" , "BSB" , "RJ" );
|
O Stream também recebeu novos métodos como dropWhile
, takeWhile
, ofNullable
e uma sobrecarga do iterate
que permite definir uma condição para que a iteração termine. Repare a semelhança com o clássico for com index no exemplo em que imprime números de 1 a 10:
Stream
.iterate( 1 , n -> n<= 10 , n -> n+ 1 )
.forEach(System.out::println);
|
Além das mudanças no Stream em si, diversas outras APIs receberam métodos que tiram proveito de seus recursos. Um exemplo seria o Optional
, que agora consegue projetar seus valores diretamente para o Stream:
Stream<Integer> stream = Optional.of(ids).stream();
|
Essa transformação é extremamente útil quando você precisa aplicar diversas operações nos valores e tirar proveito da característica lazy da API.
O java.time
também recebeu uma forma bastante interessante de retornar streams com intervalo de datas:
Stream<LocalDate> dates =
LocalDate.datesUntil(jdk10Release);
|
E o Scanner
agora possui métodos como o findAll
onde, dada uma expressão regular, retorna um stream de possíveis resultados.
String input = "esperei 3 anos pelo lançamento do java 9" ;
List<String> matches = new Scanner(input)
.findAll( "\\d+" )
.map(MatchResult::group)
.collect(toList());
|
Extensão aos recursos da linguagem
Além de novos métodos e APIs, a linguagem recebeu um suporte maior na inferência de tipos e nos recursos existentes. O try-with-resources agora pode usar variáveis efetivamente finais, sem precisar re-instanciar como em sua versão agora antiga.
O código que era assim:
public void read(BufferedReader reader) {
try (BufferedReader reader = reader) {
}
}
|
Agora pode ser escrito sem a necessidade de criar um novo tipo e da re-atribuição:
public void read(BufferedReader reader) {
try (reader) {
}
}
|
O uso do operador diamante em classes anônimas e suporte aos métodos privados em interfaces — para reutilizar código dos
default methods sem quebrar encapsulamento — são algumas das várias outras possibilidades. Muitas delas estão definidas na proposta
Milling Project Coin, que recebeu esse nome por ser um aperfeiçoamento dos recursos introduzidos no
Project Coin do JDK 7 — como o próprio operador diamante.
Performance
As melhorias relacionadas a performance também são incríveis. Um destaque especial foi a adoção do G1 como Garbage Collector padrão, como já era esperado. Ele é um algoritmo bem mais previsível, que executa seu trabalho em diferentes threads e em uma frequência muito maior, compactando o heap de memória durante a execução. O G1 também faz o reaproveitamento de Strings e tem diversos outros benefícios interessantes.
Por falar em Strings, elas agora possuem seu valor representado de uma forma muito mais eficiente em memória. Se você conferir na implementação das versões anteriores da linguagem, perceberá que uma String guarda seu valor interno em um array de caracteres, que mantém dois bytes para cada um deles.
private final char value[];
|
O problema da abordagem atual é que, na maior parte dos casos, as Strings usam valores em formato ISO-8859–1/Latin-1 que precisam de apenas um byte por caractere. Em outras palavras, poderíamos usar metade do espaço!
Bem, é o que estamos fazendo agora com Java 9! Experimente abrir o JShell e ver como esse campo de valor é declarado hoje:
jshell> String. class .getDeclaredField( "value" )
private final byte [] value;
|
Um array de bytes!
Com isso usamos apenas um byte, que será o suficiente na esmagadora maioria dos casos. Para os demais, foi adicionado um campo adicional de 1 byte para indicar qual o encoding que está sendo usado. Isso será feito automaticamente, baseado no conteúdo da String.
Ufa! Esse é resumo mínimo do que entrou na nova versão, mas acredite quando eu digo que tem muito mais além do que foi aqui mencionado. A própria Oracle amadureceu bastante suas documentações e trabalho na comunidade, com vários artigos e tutoriais bem interessantes. Vou deixar alguns links aqui pra quem quiser se aprofundar mais.
Onde estudar mais?
Entre os documentos oficiais dos arquitetos da plataforma, você certamente poderá se interessar em dar uma olhada
nos release notes, com links inclusive para alguns tutoriais de migração, na
lista com todas as especificações que entraram, além de
alguns videos de experts introduzindo as mudanças.
No livro
Java 9: Interativo, reativo e modularizado eu entro a fundo nas principais mudanças colocando tudo em um projeto prático, que no final é modularizado e você sente com as mãos na massa como é o processo de migração e os grandes benefícios da nova arquitetura modular da plataforma.
Também falei um pouco sobre as mudanças
em um episódio da Alura Live, junto com o Gabriel — nosso host — e o Phil, que trabalha conosco no time de desenvolvimento da Alura.
Logo você também deve ver mais um monte de conteúdo aberto nosso sobre o assunto, entrando a fundo em detalhes específicos das muitas novidades da versão