Boa noite, colegas. Exatamente há um mês, recebemos um contrato para a tradução do
Java moderno da Manning, que deve ser um dos nossos novos produtos mais notáveis no próximo ano. O problema de "Moderno" e "Legado" em Java é tão agudo que a necessidade de um livro desses é bastante madura. A escala do desastre e como resolver problemas no Java 9 são brevemente descritas em um artigo de Wayne Citrin, cuja tradução queremos oferecer hoje.
A cada poucos anos, com o lançamento de uma nova versão do Java, os palestrantes do JavaOne começam a apreciar novas construções de linguagem e APIs e elogiam suas virtudes. E desenvolvedores zelosos, enquanto isso, estão ansiosos para introduzir novos recursos. Essa imagem está longe da realidade - ela não leva em consideração que a maioria dos programadores está ocupada
dando suporte e finalizando os aplicativos existentes , e não grava novos aplicativos do zero.
A maioria dos aplicativos - especialmente os comerciais - deve ser compatível com versões anteriores do Java que não suportam todos esses novos recursos do super-duper. Por fim, a maioria dos clientes e usuários finais, especialmente no segmento de grandes empresas, desconfia de uma atualização radical da plataforma Java, preferindo esperar até que fique mais forte.
Portanto, assim que o desenvolvedor tenta uma nova oportunidade, ele se depara com problemas. Você usaria os métodos de interface padrão no seu código? Talvez - se você tiver sorte e seu aplicativo não precisar interagir com o Java 7 ou inferior. Deseja usar a classe
java.util.concurrent.ThreadLocalRandom
para gerar números pseudo-aleatórios em um aplicativo multithread? Não funcionará se seu aplicativo for executado em Java 6, 7, 8 ou 9 ao mesmo tempo.
Com o lançamento do novo lançamento, os desenvolvedores que suportam código legado se sentem como crianças forçadas a olhar para uma vitrine de pastelaria. Eles não podem entrar, então o destino deles é decepção e frustração.
Então, há algo na nova versão do Java 9 para programadores envolvidos no suporte a código legado? Algo que poderia facilitar sua vida? Felizmente sim.
O que tinha que ser feito com o suporte do código legado é a aparência do Java 9Obviamente, você pode colocar os recursos da nova plataforma em
aplicativos herdados nos quais você deve cumprir a compatibilidade com versões anteriores. Em particular, sempre há oportunidades para tirar proveito das novas APIs. No entanto, pode ficar um pouco feio.
Por exemplo, você pode aplicar a ligação tardia se desejar acessar a nova API quando seu aplicativo também precisar trabalhar com versões mais antigas do Java que não suportam essa API. Suponha que você queira usar a classe
java.util.stream.LongStream
, introduzida no Java 8, e que você queira usar o
anyMatch(LongPredicate)
dessa classe, mas o aplicativo deve ser compatível com o Java 7. Você pode criar uma classe auxiliar, desta forma:
public classLongStreamHelper { private static Class longStreamClass; private static Class longPredicateClass; private static Method anyMatchMethod; static { try { longStreamClass = Class.forName("java.util.stream.LongStream"); longPredicateClass = Class.forName("java.util.function.LongPredicate"); anyMatchMethod = longStreamClass.getMethod("anyMatch", longPredicateClass): } catch (ClassNotFoundException e) { longStreamClass = null; longPredicateClass = null; anyMatchMethod = null } catch (NoSuchMethodException e) { longStreamClass = null; longPredicateClass = null; anyMatchMethod = null; } public static boolean anyMatch(Object theLongStream, Object thePredicate) throws NotImplementedException { if (longStreamClass == null) throw new NotImplementedException(); try { Boolean result = (Boolean) anyMatchMethod.invoke(theLongStream, thePredicate); return result.booleanValue(); } catch (Throwable e) {
Existem maneiras de simplificar essa operação ou torná-la mais geral ou mais eficaz - você entendeu a idéia.
Em vez de chamar
theLongStream.anyMatch(thePredicate)
, como faria no Java 8, você pode chamar
LongStreamHelper.anyMatch(theLongStream, thePredicate)
em qualquer versão do Java. Se você estiver lidando com o Java 8, isso funcionará, mas se com o Java 7, o programa lançará uma
NotImplementedException
.
Por que isso é feio? Como o código pode se tornar excessivamente complicado se você precisar acessar muitas APIs (na verdade, mesmo agora, com uma única API, isso já é inconveniente). Além disso, essa prática não é segura para o tipo, pois o código não pode mencionar diretamente
LongStream
ou
LongPredicate
. Finalmente, essa prática é muito menos eficiente, devido à sobrecarga da reflexão, além
try-catch
blocos adicionais de
try-catch
. Portanto, embora possa ser feito dessa maneira, não é muito interessante e está repleto de erros devido ao descuido.
Sim, você pode acessar as novas APIs e seu código ao mesmo tempo mantém a compatibilidade com versões anteriores, mas você não terá êxito com as novas construções de idioma. Por exemplo, suponha que precisamos usar expressões lambda no código que deve permanecer compatível com versões anteriores e funcionar no Java 7. Você está sem sorte. O compilador Java não permitirá que você especifique uma versão do código-fonte acima do destino. Portanto, se você definir o nível de conformidade do código-fonte como 1,8 (ou seja, Java 8) e o nível de conformidade de destino for 1,7 (Java 7), o compilador não permitirá.
Arquivos JAR com várias versões ajudarão vocêMais recentemente, outra grande oportunidade apareceu para usar os recursos Java mais recentes, permitindo que os aplicativos funcionassem com versões mais antigas do Java, onde esses aplicativos não eram suportados. No Java 9, esse recurso é fornecido para novas APIs e novas construções de linguagem Java: estamos falando de
arquivos JAR com
várias versões .
Os arquivos JAR com várias versões quase não diferem dos bons e antigos arquivos JAR, mas com uma ressalva importante: um novo "nicho" apareceu nos novos arquivos JAR, onde é possível escrever classes que usam os recursos mais recentes do Java 9. Se você trabalha com o Java 9, A JVM encontrará esse "nicho", usará as classes dele e ignorará as classes com o mesmo nome da parte principal do arquivo JAR.
No entanto, ao trabalhar com Java 8 ou inferior, a JVM não está ciente da existência desse "nicho". Ela o ignora e usa classes da parte principal do arquivo JAR. Com o lançamento do Java 10, um novo “nicho” semelhante aparecerá para as classes que usam os recursos mais relevantes do Java 10 e assim por diante.
No
JEP 238 , uma proposta para desenvolvimento Java, que descreve arquivos JAR vorazes, é fornecido um exemplo simples. Digamos que temos um arquivo JAR com quatro classes em execução no Java 8 ou inferior:
JAR root - A.class - B.class - C.class - D.class
Agora imagine que, após o lançamento do Java 9, reescrevamos as classes A e B para que eles possam usar os novos recursos específicos do Java 9. Em seguida, o Java 10 é lançado e reescrevemos a classe A para que ele possa usar os novos recursos do Java 10. , o aplicativo ainda deve funcionar bem com o Java 8. O novo arquivo JAR com várias versões se parece com o seguinte:
JAR root - A.class - B.class - C.class - D.class - META-INF Versions - 9 - A.class - B.class - 10 - A.class
O arquivo JAR não adquiriu apenas uma nova estrutura; agora em seu manifesto, é indicado que esse arquivo possui várias versões.
Quando você executa esse arquivo JAR na JVM Java 8, ele ignora a seção
\META-INF\Versions
porque nem sequer suspeita ou procura por ele. Somente as classes originais A, B, C e D são usadas.
Ao executar no Java 9, as classes localizadas em
\META-INF\Versions\9
são usadas, além disso, são usadas em vez das classes originais A e B, mas as classes em
\META-INF\Versions\10
ignoradas.
Ao executar no Java 10, os dois ramos
\META-INF\Versions
; em particular, a versão A do Java 10, versão B do Java 9 e as versões padrão C e D.
Portanto, se você precisa de uma nova API ProcessBuilder do Java 9 em seu aplicativo, mas precisa garantir que o aplicativo continue funcionando no Java 8, basta escrever as novas versões de suas classes usando ProcessBuilder na seção
\META-INF\Versions\9
do arquivo JAR e deixe as classes antigas na parte principal do arquivo, usadas por padrão. Essa é a maneira mais fácil de usar os novos recursos do Java 9 sem sacrificar a compatibilidade com versões anteriores.
O Java 9 JDK possui uma versão da ferramenta jar.exe que suporta a criação de arquivos JAR com várias versões. Outras ferramentas não JDK também fornecem esse suporte.
Java 9: módulos, módulos em todos os lugaresO sistema do módulo Java 9 (também conhecido como Projeto Jigsaw) é, sem dúvida, a maior mudança no Java 9. Um dos objetivos de modularização é fortalecer o mecanismo de encapsulamento do Java para que o desenvolvedor possa especificar quais APIs são fornecidas a outros componentes e contar, que a JVM aplicará o encapsulamento. O encapsulamento é mais poderoso com a modularização do que com modificadores de acesso
public/protected/private
para classes ou membros da classe.
O segundo objetivo da modularização é indicar quais módulos precisam de outros módulos para funcionar e, antes de iniciar o aplicativo, verifique se todos os módulos necessários estão no lugar. Nesse sentido, os módulos são mais fortes que o mecanismo tradicional do caminho de classe, pois os caminhos do caminho de classe não são verificados com antecedência e os erros são possíveis devido à falta de classes necessárias. Portanto, um caminho de classe incorreto já pode ser detectado quando o aplicativo tiver tempo para trabalhar por tempo suficiente ou após ter sido iniciado várias vezes.
Todo o sistema do módulo é amplo e complexo, e uma discussão detalhada está além do escopo deste artigo (aqui está uma boa
explicação detalhada ). Aqui prestarei atenção aos aspectos da modularização que ajudam o desenvolvedor com o suporte de aplicativos herdados.
A modularização é uma coisa boa, e o desenvolvedor deve tentar dividir o novo código em módulos sempre que possível, mesmo que o restante do aplicativo seja (ainda não) modularizado. Felizmente, isso é fácil de fazer, graças à especificação para trabalhar com módulos.
Primeiro, o arquivo JAR se torna modularizado (e se transforma em um módulo) com a aparência do arquivo module-info.class (compilado a partir de module-info.java) na raiz do arquivo JAR.
module-info.java
contém metadados, em particular, o nome do módulo cujos pacotes são exportados (ou seja, tornam-se visíveis de fora), quais módulos esse módulo requer e outras informações.
As informações no
module-info.class
são visíveis apenas quando a JVM a procura - ou seja, o sistema trata os arquivos JAR modularizados da mesma forma normal se funcionar com versões mais antigas do Java (pressupõe-se que o código tenha sido compilado para funcionar com uma versão mais antiga do Java Estritamente falando, é preciso um pouco de química e ainda é o Java 9 que é especificado como a versão de destino do module-info.class, mas isso é real).
Portanto, você deve poder executar arquivos JAR modularizados com Java 8 e abaixo, desde que em outros aspectos eles também sejam compatíveis com versões anteriores do Java. Observe também que
module-info.class
podem, com reservas,
ser colocados em áreas com versão de arquivos JAR com várias versões .
No Java 9, há um caminho de classe e um caminho do módulo. e um caminho do módulo. O caminho de classe funciona normalmente. Se você colocar um arquivo JAR modularizado em um caminho de classe, ele será desperdiçado como qualquer outro arquivo JAR. Ou seja, se você modularizou o arquivo JAR e seu aplicativo ainda não está pronto para tratá-lo como um módulo, você pode colocá-lo no caminho de classe, ele funcionará como sempre. Seu código legado deve lidar com isso com êxito.
Observe também que a coleção de todos os arquivos JAR no caminho de classe é considerada parte de um único módulo sem nome. Esse módulo é considerado o mais comum, no entanto, exporta todas as informações para outros módulos e pode se referir a qualquer outro módulo. Portanto, se você ainda não possui um aplicativo Java modularizado, mas existem algumas bibliotecas antigas que também não são modularizadas (e provavelmente nunca o serão) - você pode simplesmente colocar todas essas bibliotecas no caminho de classe e todo o sistema funcionará bem.
O Java 9 possui um caminho de módulo que funciona junto com o caminho de classe. Ao usar módulos desse caminho, a JVM pode verificar (em tempo de compilação e em tempo de execução) se todos os módulos necessários estão no lugar e reportar um erro se algum módulo estiver ausente. Todos os arquivos JAR no caminho de classe, como membros de um módulo sem nome, são acessíveis aos módulos listados no caminho modular - e vice-versa.
Não é difícil transferir o arquivo JAR do caminho de classe para o caminho do módulo - e aproveitar ao máximo a modularização. Primeiro, você pode adicionar o arquivo
module-info.class
ao arquivo JAR e, em seguida, colocar o arquivo JAR modularizado no caminho dos módulos. Esse módulo recém-criado ainda poderá acessar todos os arquivos JAR restantes no JAR do caminho de classe, pois eles entram no módulo sem nome e permanecem em acesso.
Também é possível que você não queira modular o arquivo JAR ou que o arquivo JAR não pertença a você, mas a outra pessoa, portanto, você não pode modulá-lo você mesmo. Nesse caso, o arquivo JAR ainda pode ser colocado no caminho do módulo, tornando-se um módulo automático.
Um módulo automático é considerado um módulo, mesmo que não possua um
module-info.class
. Este módulo é o mesmo nome que o arquivo JAR que ele contém e outros módulos podem solicitá-lo explicitamente. Ele exporta automaticamente todas as suas APIs disponíveis ao público e lê (ou seja, requer) todos os outros módulos nomeados, bem como os módulos sem nome.
Portanto, um arquivo JAR não modulado de um caminho de classe pode ser transformado em um módulo sem fazer nada. Os arquivos JAR herdados são convertidos automaticamente em módulos, apenas faltam algumas informações que determinariam se todos os módulos necessários estão no lugar ou determinar o que está faltando.
Nem todo arquivo JAR não modulado pode ser movido para o caminho do módulo e transformado em um módulo automático. Existe uma regra: um
pacote pode fazer parte de apenas um módulo nomeado . Ou seja, se um pacote estiver localizado em mais de um arquivo JAR, apenas um arquivo JAR com este pacote na composição poderá ser transformado em um módulo automático. O restante pode permanecer no caminho de classe e ingressar no módulo sem nome.
À primeira vista, o mecanismo descrito aqui parece complicado, mas na prática é muito simples. De fato, nesse caso, é possível deixar os arquivos JAR antigos no caminho de classe ou movê-los para o caminho do módulo. Você pode dividi-los em módulos ou não. E quando seus arquivos JAR antigos são modularizados, você pode deixá-los no caminho de classe ou movê-los para o caminho do módulo.
Na maioria dos casos, tudo deve simplesmente funcionar, como antes. Seus arquivos JAR herdados devem se enraizar no novo sistema modular. Quanto mais você modula o código, mais informações de dependência você precisa verificar, e os módulos e APIs ausentes serão detectados em estágios muito iniciais do desenvolvimento e provavelmente economizarão muito trabalho.
Java 9 “faça você mesmo”: JDK modular e JlinkUm dos problemas com aplicativos Java herdados é que o usuário final pode não funcionar com um ambiente Java adequado. Uma maneira de garantir a integridade de um aplicativo Java é fornecer um tempo de execução com o aplicativo. Java permite criar JREs privados (redistribuíveis) que podem ser distribuídos no aplicativo.
Aqui está como criar um JRE particular. Como regra geral, é tomada a hierarquia de arquivos JRE, que é instalada com o JDK, os arquivos necessários são salvos e os arquivos opcionais com a funcionalidade que pode ser necessária em seu aplicativo são salvos.
O processo é um pouco problemático: você deve manter uma hierarquia de arquivos de instalação, tendo cuidado, para não perder um único arquivo, nem um único diretório. Isso por si só não vai doer, no entanto, você ainda deseja se livrar de tudo o que é supérfluo, pois esses arquivos ocupam espaço. Sim, é fácil ceder e cometer esse erro.
Então, por que não delegar esse trabalho ao JDK?
No Java 9, você pode criar um ambiente independente que é adicionado ao aplicativo - e nesse ambiente, haverá todo o necessário para o aplicativo funcionar. Você não precisa mais se preocupar com o fato de o computador do usuário ter o ambiente errado para a execução do Java, não com o fato de você mesmo ter construído incorretamente um JRE particular.
Um recurso essencial para a criação de
imagens executáveis independentes é um sistema modular. Agora você pode modularizar não apenas seu próprio código, mas também o próprio Java 9 JDK. Agora a biblioteca de classes Java é uma coleção de módulos, e as ferramentas JDK também consistem em módulos. O sistema de módulos requer que você especifique os módulos da classe base que são necessários no seu código e especifique os elementos JDK necessários.
Para reunir tudo isso, o Java 9 fornece uma nova ferramenta especial chamada
jlink . Ao iniciar o jlink, você obtém uma hierarquia de arquivos - exatamente os necessários para executar seu aplicativo, nem mais nem menos. Esse conjunto será muito menor que o JRE padrão; além disso, será específico da plataforma (ou seja, será selecionado para um sistema operacional e uma máquina específicos). Portanto, se você deseja criar essas imagens executáveis para outras plataformas, será necessário executar o jlink no contexto da instalação em cada plataforma específica para a qual você precisa dessa imagem.
Observe também: se você executar o jlink com um aplicativo no qual nada é modularizado, a ferramenta simplesmente não terá as informações necessárias para compactar o JRE, portanto, o jlink não terá mais nada a não ser empacotar todo o JRE. Mesmo nesse caso, será um pouco mais conveniente para você: o jlink compilará o JRE para você, para que você não se preocupe em como copiar corretamente a hierarquia de arquivos.
Com o jlink, fica fácil compactar o aplicativo e tudo o que você precisa para executá-lo - e você não precisa se preocupar em fazer algo errado. A ferramenta empacotará apenas a parte do tempo de execução necessária para o aplicativo funcionar. Ou seja, é garantido que um aplicativo Java herdado receba um ambiente no qual estará operacional.
A reunião do velho e do novo, Java- , , . Java 9, , API , ( ) , , Java.
Java 9: -, , , Java.
JAR- Java 9 JAR-, Java . , Java 9, Java 8 – .
Java, , JAR- , . , , « » .
JDK jlink, , . , Java – .
Java, Java 9 , – , , Java.