Executando programas de arquivo único no Java 11 sem compilação



Deixe o arquivo de origem HelloUniverse.java conter uma definição de classe e um método main estático que gera uma única linha de texto no terminal:

 public class HelloUniverse{ public static void main(String[] args) { System.out.println("Hello InfoQ Universe"); } } 

Normalmente, para executar esta classe, você deve primeiro compilá-la usando o compilador Java (javac), que cria o arquivo HelloUniverse.class:

 mohamed_taman$ javac HelloUniverse.java 

Em seguida, você precisa executar o arquivo resultante usando o comando Java virtual machine (intérprete):

 mohamed_taman$ java HelloUniverse Hello InfoQ Universe 

Em seguida, o virtualka será iniciado primeiro, o que carregará a classe e executará o código.

E se você precisar verificar rapidamente um pedaço de código? Ou você é novo em Java ( neste caso, um ponto chave ) e deseja experimentar a linguagem? Os dois passos descritos podem complicar as coisas.

No Java SE 11, você pode executar diretamente arquivos de origem únicos sem compilação intermediária.

Esse recurso é especialmente útil para iniciantes que desejam trabalhar com programas simples. Combinado com o jshell, você obtém um ótimo conjunto de ferramentas para educar iniciantes.

Os profissionais podem usar essas ferramentas para aprender inovações no idioma ou testar APIs desconhecidas. Em nossa opinião, é melhor automatizar muitas tarefas, como escrever programas Java na forma de scripts com execução subsequente a partir do shell do SO. Como resultado, podemos trabalhar de maneira flexível com scripts de shell e usar todos os recursos do Java. Vamos falar sobre isso com mais detalhes na segunda parte do artigo.

Esse ótimo recurso do Java 11 permite executar diretamente um único arquivo de origem sem compilação. Vamos discutir.

Do que você precisa


Para executar o código fornecido no artigo, você precisa da versão Java não inferior a 11. No momento da redação, a versão atual era o Java SE Development Kit 12.0.1 - a versão final está aqui , basta aceitar os termos da licença e clicar no link para seu sistema operacional. Se quiser experimentar os recursos mais recentes, é possível fazer o download do acesso antecipado do JDK 13.

Observe que agora também estão disponíveis versões de vários fornecedores do OpenJDK, incluindo o AdoptOpenJDK .

Neste artigo, usaremos um editor de texto sem formatação em vez do Java IDE para evitar toda a magia do IDE e usar a linha de comando Java diretamente no terminal.

Execute .java com Java


A função JEP 330 (executando programas de arquivo único com código-fonte) apareceu no JDK 11. Permite executar diretamente os arquivos de origem com o código-fonte Java, sem usar um intérprete. O código-fonte é compilado na memória e, em seguida, executado pelo intérprete sem criar um arquivo .class no disco.

No entanto, essa função é limitada ao código armazenado em um único arquivo. Você não pode executar vários arquivos de origem de uma só vez.

Para contornar essa limitação, todas as classes devem ser definidas em um único arquivo. Não há restrições em seu número. Além disso, embora estejam no mesmo arquivo, não importa se são públicos ou privados.

A primeira classe definida no arquivo será considerada a principal e o método principal deve ser colocado nele. Ou seja, a ordem é importante.

Primeiro exemplo


Vamos começar com o exemplo mais simples clássico - Olá Universo!

Demonstraremos o recurso descrito com vários exemplos para que você tenha uma idéia de como ele pode ser usado na programação diária.

Crie um arquivo HelloUniverse.java com o código do início do artigo, compile e execute o arquivo de classe resultante. Em seguida, exclua-o, agora você entenderá o porquê:

 mohamed_taman$ rm HelloUniverse.class 

Se agora você estiver usando o interpretador Java, execute o arquivo de classe sem compilação:

 mohamed_taman$ java HelloUniverse.java Hello InfoQ Universe 

você verá o mesmo resultado: o arquivo será executado.

Isso significa que agora você pode simplesmente executar o java HelloUniverse.java . Transferimos o próprio código-fonte, e não o arquivo de classe: o sistema em si o compila, inicia e exibe uma mensagem no console.

Ou seja, a compilação ainda é realizada sob o capô. E, no caso de seu erro, receberemos uma notificação sobre isso. Você pode verificar a estrutura do diretório e garantir que o arquivo de classe não seja gerado, a compilação seja realizada na memória.

Agora vamos descobrir como tudo funciona.

Como o interpretador Java executa o programa HelloUniverse


No JDK 10, o iniciador Java pode operar em três modos:

  1. Execução do arquivo de classe.
  2. Execução da classe principal a partir de um arquivo JAR.
  3. Execução da classe principal do módulo.

E no Java 11, um quarto modo apareceu:

  1. Execução da classe declarada no arquivo de origem.

Nesse modo, o arquivo de origem é compilado na memória e, em seguida, a primeira classe desse arquivo é executada.

O sistema determina sua intenção de inserir o arquivo de origem de acordo com dois critérios:

  1. O primeiro item na linha de comando não é uma opção nem parte de uma opção.
  2. A linha pode conter a opção --source <vrsion> .

No primeiro caso, o Java descobrirá primeiro se o primeiro elemento do comando é uma opção ou parte dele. Se esse é um nome de arquivo que termina em .java, o sistema o considerará o código-fonte que precisa ser compilado e executado. Você também pode adicionar opções ao comando Java antes do nome do arquivo de origem. Por exemplo, se você deseja definir o caminho da classe quando o arquivo de origem usa dependências externas.

No segundo caso, o modo de trabalhar com o arquivo de origem é selecionado e o primeiro elemento na linha de comando, que não é uma opção, é considerado o arquivo de origem que precisa ser compilado e executado.

Se o arquivo não tiver a extensão .java, será necessário usar a opção --source para forçá-lo a entrar no modo de trabalhar com o arquivo de origem.

Isso é importante nos casos em que o arquivo de origem é um "script" que precisa ser executado e o nome do arquivo não está em conformidade com as convenções usuais para nomear os arquivos de origem com código Java.

Usando a opção --source , --source pode determinar a versão do idioma de origem. Falaremos sobre isso abaixo.

Posso passar argumentos na linha de comando?


Vamos expandir nosso programa Hello Universe para que ele mostre uma saudação pessoal a qualquer usuário que visite o Universo InfoQ:

 public class HelloUniverse2{ public static void main(String[] args){ if ( args == null || args.length< 1 ){ System.err.println("Name required"); System.exit(1); } var name = args[0]; System.out.printf("Hello, %s to InfoQ Universe!! %n", name); } } 

Salve o código no arquivo Greater.java. Observe que o nome do arquivo não corresponde ao nome da classe pública. Isso viola as regras da especificação Java.

Execute o código:

 mohamed_taman$ java Greater.java "Mo. Taman" Hello, Mo. Taman to InfoQ universe!! 

Como você pode ver, não importa em nada que os nomes de classe e arquivo não correspondam. Um leitor atento também pode perceber que passamos argumentos para o código após processar o nome do arquivo. Isso significa que qualquer argumento na linha de comando após o nome do arquivo é passado para o método principal padrão.

Determine o nível do código-fonte usando a opção --source


Existem dois cenários para usar a opção --source :

  1. Determinando o nível do código fonte.
  2. Força o tempo de execução Java no modo de origem.

No primeiro caso, se você não especificou o nível do código fonte, a versão atual do JDK será utilizada. E no segundo caso, arquivos com extensões diferentes de .java podem ser transferidos para compilação e execução em tempo real.

Vamos olhar primeiro para o segundo cenário. Renomeie o arquivo Greater.java simplesmente para o maior sem extensão e tente executar:

 mohamed_taman$ java greater "Mo. Taman" Error: Could not find or load main class greater Caused by: java.lang.ClassNotFoundException: greater 

Na ausência da extensão .java, o interpretador de comandos procura a classe compilada pelo nome passado como argumento - este é o primeiro modo de operação do iniciador Java. Para impedir que isso aconteça, use a opção --source para forçar a mudança para o modo de arquivo de origem:

 mohamed_taman$ java --source 11 greater "Mo. Taman" Hello, Mo. Taman to InfoQ universe!! 

Agora vamos para o primeiro cenário. A classe Greater.java é compatível com o JDK 10 porque contém a palavra-chave var , mas não é compatível com o JDK 9. Altere a source para 10 :

 mohamed_taman$ java --source 10 Greater.java "Mo. Taman" Hello Mo. Taman to InfoQ universe!! 

Execute o comando anterior novamente, mas desta vez passe --source 9 vez de 10 :

 mohamed_taman$ java --source 9 Greater.java "Mo. Taman" Greater.java:8: warning: as of release 10, 'var' is a restricted local variable type and cannot be used for type declarations or as the element type of an array var name = args[0]; ^ Greater.java:8: error: cannot find symbol var name = args[0]; ^ symbol: class var location: class HelloWorld 1 error 1 warning error: compilation failed 

Nota: o compilador avisa que var se tornou um nome de tipo restrito no JDK 10. Mas como temos uma linguagem de nível 10, a compilação continua. No entanto, ocorre uma falha porque o arquivo de origem não possui um tipo chamado var .

Tudo é simples. Agora considere o uso de várias classes.

Essa abordagem funciona com várias classes?


Sim sim.

Considere um exemplo com duas classes. O código verifica se o valor da string fornecido é um palíndromo .

Aqui está o código salvo no arquivo PalindromeChecker.java:

 import static java.lang.System.*; public class PalindromeChecker { public static void main(String[] args) { if ( args == null || args.length< 1 ){ err.println("String is required!!"); exit(1); } out.printf("The string {%s} is a Palindrome!! %b %n", args[0], StringUtils .isPalindrome(args[0])); } } public class StringUtils { public static Boolean isPalindrome(String word) { return (new StringBuilder(word)) .reverse() .toString() .equalsIgnoreCase(word); } } 

Execute o arquivo:

 mohamed_taman:code$ java PalindromeChecker.java RediVidEr The string {RediVidEr} is a Palindrome!! True 

Execute-o novamente, substituindo "RaceCar" em vez de "MadAm":

 mohamed_taman:code$ java PalindromeChecker.java RaceCar The string {RaceCar} is a Palindrome!! True 

Agora substitua "Mohamed" em vez de "RaceCar":

 mohamed_taman:code$ java PalindromeChecker.java Taman The string {Taman} is a Palindrome!! false 

Como você pode ver, você pode adicionar quantas classes públicas desejar a um arquivo de origem. Verifique se o método principal está definido primeiro. O intérprete usará a primeira classe como ponto de partida para iniciar o programa após compilar o código na memória.

Posso usar módulos?


Sim, sem limites. O código compilado na memória é executado como parte de um módulo sem nome com a opção --add-modules=ALL-DEFAULT , que fornece acesso a todos os módulos fornecidos com o JDK.

Ou seja, o código pode usar módulos diferentes sem a necessidade de definir explicitamente dependências usando module-info.java.

Vejamos o código que faz uma chamada HTTP usando a nova API do cliente HTTP introduzida no JDK 11. Observe que essas APIs foram introduzidas no Java SE 9 como um recurso experimental, mas agora elas têm o status de uma função completa do módulo java.net.http .

Neste exemplo, chamaremos uma API REST simples usando o método GET para obter uma lista de usuários. Passamos ao serviço público reqres.in/api/users?page=2 . Nós salvamos o código em um arquivo chamado UsersHttpClient.java:

 import static java.lang.System.*; import java.net.http.*; import java.net.http.HttpResponse.BodyHandlers; import java.net.*; import java.io.IOException; public class UsersHttpClient{ public static void main(String[] args) throws Exception{ var client = HttpClient.newBuilder().build(); var request = HttpRequest.newBuilder() .GET() .uri(URI.create("https://reqres.in/api/users?page=2")) .build(); var response = client.send(request, BodyHandlers.ofString()); out.printf("Response code is: %d %n",response.statusCode()); out.printf("The response body is:%n %s %n", response.body()); } } 

Execute o programa e obtenha o resultado:

 mohamed_taman:code$ java UsersHttpClient.java Response code is: 200 The response body is: {"page":2,"per_page":3,"total":12,"total_pages":4,"data":[{"id":4,"first_name":"Eve","last_name":"Holt","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/marcoramires/128.jpg"},{"id":5,"first_name":"Charles","last_name":"Morris","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/stephenmoon/128.jpg"},{"id":6,"first_name":"Tracey","last_name":"Ramos","avatar":"https://s3.amazonaws.com/uifaces/faces/twitter/bigmancho/128.jpg"}]} 

Agora você pode testar rapidamente novos recursos fornecidos por diferentes módulos sem criar seu próprio módulo.

Por que os scripts são importantes em Java?


Primeiro, vamos lembrar o que são scripts:

Um script é um programa escrito para um ambiente de tempo de execução específico que automatiza a execução de tarefas ou comandos que uma pessoa pode executar por sua vez.

A partir dessa definição geral, podemos derivar uma definição simples de uma linguagem de script - é uma linguagem de programação que utiliza construções de alto nível para interpretar e executar um comando (ou comandos) por vez.

A linguagem de script usa uma série de comandos gravados em um arquivo. Freqüentemente, essas linguagens são interpretadas (em vez de compiladas) e seguem um estilo de programação procedural (embora algumas linguagens de script também tenham as propriedades das linguagens orientadas a objetos).

Em geral, as linguagens de script são mais fáceis de aprender e mais rápidas de digitar do que as linguagens compiladas mais estruturadas, como Java, C e C ++. As linguagens de script do lado do servidor incluem Perl, PHP e Python e, no lado do cliente , JavaScript.

Por um longo tempo, Java foi considerado uma linguagem compilada, bem estruturada e altamente tipada, interpretada por uma máquina virtual para executar em qualquer arquitetura de computação. No entanto, Java não é tão fácil de aprender e criar protótipos em comparação com outras linguagens de script.

No entanto, o Java já fez 24 anos e é usado por cerca de 10 milhões de desenvolvedores em todo o mundo. Versões recentes adicionaram vários novos recursos para tornar mais fácil para os jovens programadores aprender essa linguagem, além de usar as funções da linguagem e da API sem compilação e IDE. Por exemplo, o Java SE 9 introduziu a ferramenta JShell (REPL), que suporta programação interativa.

E com o lançamento do JDK 11, essa linguagem conseguiu suportar scripts, porque agora você pode executar código com uma simples chamada ao comando java !

Existem duas maneiras principais de usar scripts no Java 11:

  1. Chamada direta ao comando java .
  2. Usando scripts * nix para a linha de comando, semelhante aos scripts do Bash.

Já consideramos a primeira opção, agora vamos lidar com a segunda. Abre muitas possibilidades para nós.

Arquivos Shebang: execute Java como um script de shell


Portanto, no Java SE 11, apareceu o suporte para scripts, incluindo arquivos shebang tradicionais do mundo * nix. Para apoiá-los, uma especificação de idioma não era necessária.

No arquivo shebang, os dois primeiros bytes devem ser 0x23 e 0x21. Este é o número de codificação de caracteres ASCII! Todos os bytes subseqüentes no arquivo são lidos com base no sistema de codificação padrão nesta plataforma.

Portanto, para que o arquivo seja executado usando o mecanismo shebang interno do SO, existe apenas um requisito: a primeira linha começa com #! .. Isso significa que não precisamos de nenhuma primeira linha especial quando o iniciador Java é usado explicitamente para executar o código do arquivo de origem, como é o caso do HelloUniverse.java.

Execute o exemplo a seguir em um terminal executando o macOS Mojave 10.14.5 . Mas primeiro, definiremos regras importantes a serem seguidas ao criar um arquivo shebang:

  • Não misture o código Java com o código da linguagem de script do seu shell script do SO.
  • Se você precisar adicionar opções de máquina virtual, especifique --source primeira opção após o nome do arquivo executável no arquivo shebang. As opções da máquina virtual incluem: --class-path , --module-path , --add-exports , --add-modules , --limit-modules , --patch-module , --upgrade-module-path , bem como quaisquer variações dos mesmos. Também está incluída nesta lista a nova opção --enable-preview , descrita no JEP 12 .
  • Você deve especificar a versão do Java que é usada no arquivo de origem.
  • A primeira linha do arquivo deve começar com caracteres shebang (#!). Por exemplo:
    #!/path/to/java --source <vrsion>
  • Para arquivos de origem Java, NÃO use o mecanismo shebang para executar arquivos que estejam em conformidade com a convenção de nomenclatura padrão (termine em .java)
  • Você deve marcar o arquivo como executável com o comando:
    chmod +x <Filname>.<Extnsion> .

Vamos criar um arquivo shebang (programa de script), que listará o conteúdo do diretório cujo nome será passado como parâmetro. Se nenhum parâmetro for passado, o diretório atual será utilizado por padrão.

 #!/usr/bin/java --source 11 import java.nio.file.*; import static java.lang.System.*; public class DirectoryLister { public static void main(String[] args) throws Exception { vardirName = "."; if ( args == null || args.length< 1 ){ err.println("Will list the current directory"); } else { dirName = args[0]; } Files .walk(Paths.get(dirName)) .forEach(out::println); } } 

Salve o código em um arquivo chamado dirlist sem a extensão e marque-o como executável: mohamed_taman:code$ chmod +x dirlist .

Execute o arquivo:

 mohamed_taman:code$ ./dirlist Will list the current directory . ./PalindromeChecker.java ./greater ./UsersHttpClient.java ./HelloWorld.java ./Greater.java ./dirlist 

Execute-o novamente usando o comando que passa no diretório pai e verifique o resultado.

 mohamed_taman:code$ ./dirlist ../ 

Nota: ao avaliar o código fonte, o intérprete ignora a linha shebang (primeira linha). Assim, o arquivo shebang pode ser chamado explicitamente usando o iniciador, por exemplo, com opções adicionais:

 $ java -Dtrace=true --source 11 dirlist 

Também deve ser observado: se o arquivo de script estiver no diretório atual, você poderá executá-lo assim:

 $ ./dirlist 

E se o script estiver em um diretório cujo caminho está especificado no PATH do usuário, você poderá executá-lo assim:

 $ dirlist 

E, finalmente, darei algumas dicas para você ter em mente ao usar scripts.

Dicas


  1. Algumas opções que você passará para o javac podem não ser passadas (ou não reconhecidas) para o java , por exemplo, as opções -processor ou -Werror .
  2. Se houver arquivos .class e .java no caminho de classe, o iniciador o forçará a usar o arquivo de classe.

     mohamed_taman:code$ javac HelloUniverse.java mohamed_taman:code$ java HelloUniverse.java error: class found on application class path: HelloUniverse 

  3. Esteja ciente da possibilidade de um conflito entre nomes de classes e pacotes. Dê uma olhada nesta estrutura de diretório:

     mohamed_taman:code$ tree . ├── Greater.java ├── HelloUniverse │ ├── java.class │ └── java.java ├── HelloUniverse.java ├── PalindromeChecker.java ├── UsersHttpClient.java ├── dirlist └── greater 

    Observe os dois java.java no pacote HelloUniverse e o arquivo HelloUniverse.java no mesmo diretório. Se você tentar executar:

     mohamed_taman:code$ java HelloUniverse.java 

    então qual arquivo será executado primeiro e qual segundo? O iniciador não se refere mais ao arquivo de classe no pacote HelloUniverse. Em vez disso, ele carregará e executará o arquivo HelloUniverse.java original, ou seja, o arquivo será iniciado no diretório atual.

Os arquivos Shebang abrem muitas possibilidades para a criação de scripts para automatizar todos os tipos de tarefas usando ferramentas Java.

Sumário


A partir do Java SE 11 e pela primeira vez no histórico de programação, é possível executar scripts diretamente com o código Java sem compilação. Isso permite escrever scripts Java e executá-los a partir da linha de comandos * nix.

Experimente esse recurso e compartilhe seu conhecimento com outras pessoas.

Fontes úteis


Source: https://habr.com/ru/post/pt483888/


All Articles