
Deje que el archivo fuente HelloUniverse.java contenga una definición de clase y un método
main
estático que genere una sola línea de texto al terminal:
public class HelloUniverse{ public static void main(String[] args) { System.out.println("Hello InfoQ Universe"); } }
Por lo general, para ejecutar esta clase, primero debe compilarla utilizando el compilador de Java (javac), que crea el archivo HelloUniverse.class:
mohamed_taman$ javac HelloUniverse.java
Luego, debe ejecutar el archivo resultante con el comando de máquina virtual Java (intérprete):
mohamed_taman$ java HelloUniverse Hello InfoQ Universe
Luego, el virtualka comenzará primero, lo que cargará la clase y ejecutará el código.
¿Y si necesita verificar rápidamente un código? ¿O eres nuevo en Java (
en este caso, un punto clave ) y quieres experimentar con el lenguaje? Los dos pasos descritos pueden complicar las cosas.
En Java SE 11, puede ejecutar directamente archivos de origen único sin compilación intermedia.
Esta característica es especialmente útil para principiantes que desean trabajar con programas simples. En combinación con jshell, obtienes un gran conjunto de herramientas para enseñar a principiantes.
Los profesionales pueden usar estas herramientas para aprender innovaciones en el lenguaje o probar API desconocidas. En nuestra opinión, es mejor automatizar muchas tareas, como escribir programas Java en forma de scripts con posterior ejecución desde el shell del sistema operativo. Como resultado, podemos trabajar de manera flexible con scripts de shell y usar todas las características de Java. Hablemos de esto con más detalle en la segunda parte del artículo.
Esta gran característica de Java 11 le permite ejecutar directamente un solo archivo fuente sin compilación. Hablemos
Que necesitas
Para ejecutar el código provisto en el artículo, necesita una versión de Java no inferior a 11. En el momento de la escritura, la versión actual era Java SE Development Kit 12.0.1: la versión final está
aquí , solo acepte los términos de la licencia y haga clic en el enlace de su sistema operativo. Si desea experimentar con las últimas funciones, puede
descargar el acceso anticipado JDK 13.
Tenga en cuenta que las versiones de varios proveedores de OpenJDK ahora también están disponibles, incluido
AdoptOpenJDK .
En este artículo, utilizaremos un editor de texto sin formato en lugar del IDE de Java para evitar toda la magia del IDE, y utilizaremos la línea de comandos de Java directamente en el terminal.
Ejecute .java con Java
La función
JEP 330 (ejecutar programas de un solo archivo con código fuente) apareció en JDK 11. Le permite ejecutar directamente archivos fuente con código fuente Java, sin usar un intérprete. El código fuente se compila en la memoria y luego el intérprete lo ejecuta sin crear un archivo .class en el disco.
Sin embargo, esta función está limitada al código que se almacena en un solo archivo. No puede ejecutar varios archivos fuente a la vez.
Para evitar esta limitación, todas las clases deben definirse en un solo archivo. No hay restricciones en su número. Además, mientras están en el mismo archivo, no importa si son públicos o privados.
La primera clase definida en el archivo se considerará la principal, y el método principal debe colocarse en ella. Es decir, el orden es importante.
Primer ejemplo
Comencemos con el ejemplo clásico más simple: ¡Hola Universo!
Demostraremos la característica descrita con varios ejemplos para que tenga una idea de cómo se puede usar en la programación diaria.
Cree un archivo HelloUniverse.java con el código desde el principio del artículo, compile y ejecute el archivo de clase resultante. Luego bórrelo, ahora comprenderá por qué:
mohamed_taman$ rm HelloUniverse.class
Si ahora usa el intérprete de Java, ejecuta el archivo de clase sin compilación:
mohamed_taman$ java HelloUniverse.java Hello InfoQ Universe
verá el mismo resultado: se ejecutará el archivo.
Esto significa que ahora puede ejecutar
java HelloUniverse.java
. Transferimos el código fuente en sí, y no el archivo de clase: el sistema dentro de sí mismo lo compila, lo inicia y muestra un mensaje en la consola.
Es decir, la compilación todavía se realiza bajo el capó. Y en caso de su error, recibiremos una notificación al respecto. Puede verificar la estructura del directorio y asegurarse de que el archivo de clase no se genera, la compilación se realiza en la memoria.
Ahora veamos cómo funciona todo.
Cómo el intérprete de Java ejecuta el programa HelloUniverse
En JDK 10, el iniciador de Java puede funcionar en tres modos:
- Ejecución del archivo de clase.
- Ejecución de la clase principal desde un archivo JAR.
- Ejecución de la clase principal del módulo.
Y en Java 11, apareció un cuarto modo:
- Ejecución de la clase declarada en el archivo fuente.
En este modo, el archivo fuente se compila en la memoria y luego se ejecuta la primera clase de este archivo.
El sistema determina su intención de ingresar el archivo fuente de acuerdo con dos criterios:
- El primer elemento en la línea de comando no es una opción ni es parte de una opción.
- La línea puede contener la
--source <vrsion>
.
En el primer caso, Java primero descubrirá si el primer elemento del comando es una opción o parte de él. Si este es un nombre de archivo que termina en .java, el sistema lo considerará el código fuente que debe compilarse y ejecutarse. También puede agregar opciones al comando Java antes del nombre del archivo fuente.
Por ejemplo, si desea establecer la ruta de clase cuando el archivo fuente utiliza dependencias externas.En el segundo caso, se selecciona el modo de trabajar con el archivo fuente y el primer elemento en la línea de comando, que no es una opción, se considera el archivo fuente que debe compilarse y ejecutarse.
Si el archivo no tiene la extensión .java, debe usar la opción
--source
para forzarlo a ingresar al modo de trabajar con el archivo fuente.
Esto es importante en los casos en que el archivo fuente es un "script" que necesita ser ejecutado, y el nombre del archivo no cumple con las convenciones usuales para nombrar los archivos fuente con código Java.
Usando la opción
--source
, puede determinar la versión del idioma fuente. Hablaremos de esto a continuación.
¿Puedo pasar argumentos en la línea de comando?
Expandamos nuestro programa Hello Universe para que muestre un saludo personal a cualquier usuario que visite InfoQ Universe:
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); } }
Guarde el código en el archivo Greater.java. Tenga en cuenta que el nombre del archivo no coincide con el nombre de la clase pública. Esto viola las reglas de la especificación Java.
Ejecuta el código:
mohamed_taman$ java Greater.java "Mo. Taman" Hello, Mo. Taman to InfoQ universe!!
Como puede ver, no importa en absoluto que la clase y los nombres de los archivos no coincidan. Un lector atento también puede notar que pasamos argumentos al código después de procesar el nombre del archivo. Esto significa que cualquier argumento en la línea de comando que sigue al nombre del archivo se pasa al método principal estándar.
Determine el nivel del código fuente usando la opción --source
Hay dos escenarios para usar la opción
--source
:
- Determinación del nivel de código fuente.
- Forzar el tiempo de ejecución de Java al modo fuente.
En el primer caso, si no especificó el nivel del código fuente, se toma la versión actual del JDK. Y en el segundo caso, los archivos con extensiones distintas a .java se pueden transferir para su compilación y ejecución sobre la marcha.
Veamos primero el segundo escenario. Cambie el nombre de Greater.java simplemente a mayor sin extensión e intente ejecutar:
mohamed_taman$ java greater "Mo. Taman" Error: Could not find or load main class greater Caused by: java.lang.ClassNotFoundException: greater
En ausencia de la extensión .java, el intérprete de comandos busca la clase compilada por el nombre que se pasa como argumento; este es el primer modo de funcionamiento del iniciador de Java. Para evitar que esto suceda, use la opción
--source
para forzar el cambio al modo de archivo fuente:
mohamed_taman$ java --source 11 greater "Mo. Taman" Hello, Mo. Taman to InfoQ universe!!
Ahora pasemos al primer escenario. La clase Greater.java es compatible con JDK 10 porque contiene la palabra clave
var
, pero no es compatible con JDK 9. Cambie la
source
a
10
:
mohamed_taman$ java --source 10 Greater.java "Mo. Taman" Hello Mo. Taman to InfoQ universe!!
Ejecute el comando anterior nuevamente, pero esta vez pase
--source 9
lugar 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: el compilador advierte que
var
ha convertido en un nombre de tipo restringido en JDK 10. Pero como tenemos un lenguaje de nivel 10, la compilación continúa. Sin embargo, se produce un bloqueo porque el archivo de origen no tiene un tipo denominado
var
.
Todo es simple Ahora considere el uso de varias clases.
¿Este enfoque funciona con múltiples clases?
Si lo hace
Considere un ejemplo con dos clases. El código verifica si el valor de cadena dado es un
palíndromo .
Aquí está el código guardado en el archivo 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); } }
Ejecute el archivo
mohamed_taman:code$ java PalindromeChecker.java RediVidEr The string {RediVidEr} is a Palindrome!! True
Ejecútelo nuevamente, sustituyendo "RaceCar" en lugar de "MadAm":
mohamed_taman:code$ java PalindromeChecker.java RaceCar The string {RaceCar} is a Palindrome!! True
Ahora sustituya "Mohamed" en lugar de "RaceCar":
mohamed_taman:code$ java PalindromeChecker.java Taman The string {Taman} is a Palindrome!! false
Como puede ver, puede agregar tantas clases públicas como desee a un archivo fuente. Asegúrese de que el método principal se define primero. El intérprete usará la primera clase como punto de partida para iniciar el programa después de compilar el código en la memoria.
¿Puedo usar módulos?
Si, sin limites. El código compilado en memoria se ejecuta como parte de un módulo sin nombre con la
--add-modules=ALL-DEFAULT
, que da acceso a todos los módulos enviados con el JDK.
Es decir, el código puede usar diferentes módulos sin la necesidad de definir explícitamente dependencias usando module-info.java.
Veamos el código que realiza una llamada HTTP utilizando la nueva API de cliente HTTP introducida en JDK 11. Tenga en cuenta que estas API se introdujeron en Java SE 9 como una característica experimental, pero ahora tienen el estado de una función completa del módulo java.net.http .
En este ejemplo, llamaremos a una API REST simple usando el método GET para obtener una lista de usuarios. Pasamos al servicio público
reqres.in/api/users?page=2 . Guardamos el código en un archivo llamado 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()); } }
Ejecute el programa y obtenga el 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"}]}
Ahora puede probar rápidamente las nuevas funciones proporcionadas por diferentes módulos sin crear su propio módulo.
¿Por qué son importantes los scripts en Java?
Primero, recordemos qué scripts son:
Un script es un programa escrito para un entorno de tiempo de ejecución específico que automatiza la ejecución de tareas o comandos que una persona puede ejecutar a su vez.
A partir de esta definición general, podemos derivar una definición simple de un lenguaje de programación: es un lenguaje de programación que utiliza construcciones de alto nivel para interpretar y ejecutar un comando (o comandos) a la vez.
El lenguaje de secuencias de comandos utiliza una serie de comandos escritos en un archivo. A menudo, estos lenguajes se interpretan (en lugar de compilarse) y se adhieren a un estilo de programación procesal (aunque algunos lenguajes de secuencias de comandos también tienen las propiedades de los lenguajes orientados a objetos).
En general, los lenguajes de secuencias de comandos son más fáciles de aprender y más rápidos de escribir que los lenguajes compilados más estructurados como Java, C y C ++. Los lenguajes de secuencias de comandos del lado del servidor incluyen Perl, PHP y Python, y en el
lado del
cliente , JavaScript.
Durante mucho tiempo, Java se consideró un lenguaje compilado bien estructurado y altamente tipado que es interpretado por una máquina virtual para ejecutarse en cualquier arquitectura informática. Sin embargo, Java no es tan fácil de aprender y crear prototipos en comparación con otros lenguajes de secuencias de comandos.
Sin embargo, Java ya cumplió 24 años, es utilizado por unos 10 millones de desarrolladores en todo el mundo. Los lanzamientos recientes han agregado una serie de nuevas características para facilitar a los programadores jóvenes aprender este idioma, así como para usar las funciones del lenguaje y la API sin compilación e IDE. Por ejemplo, Java SE 9 introdujo la herramienta JShell (REPL), que admite programación interactiva.
Y con el lanzamiento de JDK 11, este lenguaje tiene la capacidad de admitir scripts, ¡porque ahora puede ejecutar código con una simple llamada al comando
java
!
Hay dos formas principales de usar scripts en Java 11:
- Llamada directa al comando
java
.
- Usando * nix scripts para la línea de comando, similar a los scripts Bash.
Ya hemos considerado la primera opción, ahora nos ocuparemos de la segunda. Nos abre muchas posibilidades.
Archivos Shebang: ejecute Java como un script de shell
Entonces, en Java SE 11, apareció soporte para scripts, incluidos los archivos shebang tradicionales del mundo * nix. Para apoyarlos, no se requería una especificación de idioma.
En el archivo shebang, los dos primeros bytes deben ser 0x23 y 0x21. Esta es la codificación de caracteres ASCII #! .. Todos los bytes posteriores en el archivo se leen según el sistema de codificación predeterminado en esta plataforma.
Por lo tanto, para que el archivo se ejecute utilizando el mecanismo shebang incorporado en el sistema operativo, solo hay un requisito: la primera línea comienza con #! .. Esto significa que no necesitamos ninguna primera línea especial cuando se utiliza explícitamente el iniciador de Java para ejecutar el código desde el archivo fuente, como es el caso de HelloUniverse.java.
Ejecute el siguiente ejemplo en un terminal que ejecute
macOS Mojave 10.14.5 . Pero primero, definiremos reglas importantes a seguir al crear un archivo shebang:
- No mezcle el código Java con el código del lenguaje de script de su script de shell del sistema operativo.
- Si necesita agregar opciones de máquina virtual, debe especificar
--source
primera opción después del nombre del archivo ejecutable en el archivo shebang. Las opciones de máquina virtual incluyen: --class-path
, --module-path
, --add-exports
, --add-modules
, --limit-modules
, --patch-module
, --upgrade-module-path
, así como cualquier variación de los mismos. También se incluye en esta lista la nueva opción --enable-preview
, descrita en JEP 12 .
- Debe especificar la versión de Java que se utiliza en el archivo fuente.
- La primera línea del archivo debe comenzar con caracteres shebang (#!). Por ejemplo:
#!/path/to/java --source <vrsion>
- Para archivos fuente Java, NO use el mecanismo shebang para ejecutar archivos que cumplan con la convención de nomenclatura estándar (finalice en .java)
- Debe marcar el archivo como ejecutable con el comando:
chmod +x <Filname>.<Extnsion>
.
Creemos un archivo shebang (programa de script), que enumerará el contenido del directorio cuyo nombre se pasará como parámetro. Si no se pasan parámetros, el directorio actual se tomará por defecto.
#!/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); } }
Guarde el código en un archivo llamado dirlist sin la extensión y luego
mohamed_taman:code$ chmod +x dirlist
como ejecutable:
mohamed_taman:code$ chmod +x dirlist
.
Ejecute el archivo
mohamed_taman:code$ ./dirlist Will list the current directory . ./PalindromeChecker.java ./greater ./UsersHttpClient.java ./HelloWorld.java ./Greater.java ./dirlist
Ejecútelo nuevamente utilizando el comando que pasa el directorio principal y verifique el resultado.
mohamed_taman:code$ ./dirlist ../
Nota: al evaluar el código fuente, el intérprete ignora la línea shebang (primera línea). Por lo tanto, el archivo shebang se puede llamar explícitamente usando el iniciador, por ejemplo, con opciones adicionales:
$ java -Dtrace=true --source 11 dirlist
También debe tenerse en cuenta: si el archivo de secuencia de comandos está en el directorio actual, puede ejecutarlo así:
$ ./dirlist
Y si el script se encuentra en un directorio cuya ruta se especifica en la RUTA del usuario, puede ejecutarlo así:
$ dirlist
Y, por último, te daré algunos consejos para tener en cuenta al usar scripts.
Consejos
- Algunas opciones que pasará a javac pueden no pasarse (o no reconocerse) a
java
, por ejemplo, las opciones -processor
o -Werror
.
- Si hay archivos .class y .java en classpath, entonces el iniciador lo obligará a usar el archivo de clase.
mohamed_taman:code$ javac HelloUniverse.java mohamed_taman:code$ java HelloUniverse.java error: class found on application class path: HelloUniverse
- Tenga en cuenta la posibilidad de un conflicto entre los nombres de clase y paquete. Eche un vistazo a esta estructura de directorios:
mohamed_taman:code$ tree . ├── Greater.java ├── HelloUniverse │ ├── java.class │ └── java.java ├── HelloUniverse.java ├── PalindromeChecker.java ├── UsersHttpClient.java ├── dirlist └── greater
Observe los dos java.java
en el paquete HelloUniverse y el archivo HelloUniverse.java en el mismo directorio. Si intentas correr:
mohamed_taman:code$ java HelloUniverse.java
entonces, ¿qué archivo se ejecutará primero y qué segundo? El iniciador ya no hace referencia al archivo de clase en el paquete HelloUniverse. En su lugar, cargará y ejecutará el archivo original HelloUniverse.java, es decir, el archivo se iniciará en el directorio actual.
Los archivos Shebang abren muchas posibilidades para crear scripts para automatizar todo tipo de tareas utilizando herramientas Java.
Resumen
Comenzando con Java SE 11 y por primera vez en el historial de programación, puede ejecutar directamente scripts con código Java sin compilación. Esto le permite escribir scripts Java y ejecutarlos desde la línea de comando * nix.
Experimente con esta función y comparta sus conocimientos con otros.
Fuentes utiles