El JEP 445 todavía está en una fase de borrador y queda ver una implementación preliminar en las versiones iniciales del JDK 21, cuyo desarrollo recién ha comenzado al momento de comenzar esto. Sin embargo, si examinamos el contenido del JEP, nos encontramos con una propuesta muy interesante.
Históricamente, para crear un método main (es decir, el método que queremos que se lance de manera inicial cuando alguien ejecuta una aplicación desarrollada en Java), era necesario incorporar un método estático llamado main, con la signatura exacta public static void main(String[]), en alguna clase, que pasaría a ser denominada una clase principal o main class (por contener un método main). Algo como esto:
package prueba;
public class Principal {
public static void main(String[] args) {
System.out.println("Hola, mundo");
}
}
En este caso, prueba.Principal sería el nombre completamente cualificado de una clase que tiene un método estático y público llamado main, con un parámetro de tipo String[]. Intentar usar esto como clase principal provocará que la JVM ejecute ese método para iniciar el programa.
Sin embargo, las propuestas que se introducen en Java son dos nuevas que tienen que ver con la forma en la que se usan estos métodos.
Método main flexible
En primer lugar, la posibilidad de usar métodos main que no sean static ni que tengan un argumento llamado String args[] como parámetro necesariamente. De este modo, se podría escribir un método main que no sea estático:
package prueba;
public class Principal {
public void main(String[] args) {
System.out.println("Hola, mundo");
}
}
En este caso, si usamos en el futuro una JVM con Java 21 (en el momento de escribir esto todavía no está claro si habrá que hacer algo más), y tratamos de usar esta clase como clase principal, no detectará un método main estático, pero sí un método main no estático, así que Java crearía una instancia de esta clase y llamaría al método no estático main de forma automática. Para ello, será necesario que el constructor de la clase no tenga parámetros, para que Java pueda instanciar la clase. De otro modo, no sabría que cosas proporcionarle.
¿Qué ventajas tendría este enfoque? De por sí, quitarnos el modificador static tampoco parece hace mucho. Sin embargo, algunas personas tratan de pasar el mínimo tiempo posible en el método main estático tradicional, instanciando una clase Main más tradicional para poder tratarla mejor en un sistema orientado a objetos. Algo como esto:
public class Main {
public static void main(String[] args) {
var m = new Main();
m.launchMainGame();
}
public Logger logger = Logger.createLogger();
public Application createApplication() {
// Implementación de prueba de un método factory.
return new Application(logger);
}
public JFrame launchMainGame() {
// Implementación de prueba de un método factory.
return new MainGame(createApplication());
}
}
Si bien es posible hacer métodos estáticos y atributos estáticos, para algunas personas esto no resulta apropiado. A ellas, esta feature.
No obstante, Java aquí iría un paso más allá. Si tu método main no va a usar argumentos, no habrá ninguna necesidad. Es decir, si no existe un main estático (ni no estático) que acepte un parámetro de tipo String[], pero sí existe uno main, sea estático o no, que no tenga argumentos, también será válido:
public class MainClass {
void main() {
System.out.println("Hola mundo");
}
}
Significativo para cargarnos casi todas las palabras clave. En este caso el método main tampoco es público. Tiene visibilidad de paquete, pero es suficiente para que la JVM lo pueda instanciar. No se puede usar private, pero sí public, protected o package.
Clases anónimas principales
Las clases anónimas principales serían el siguiente paso. En el JEP lo describen del siguiente modo. Igual que, de toda la vida, Java ha tenido un paquete anónimo principal, que es el que se usa cuando no hay paquete en primer lugar (es decir, el código está tal cual dentro de la carpeta src o src/main/java), se va a introducir el concepto de clase anónima principal.
Antes que nada, ¿qué es el paquete anónimo principal realmente? Como probablemente sepas, el código en Java normalmente lo metes en un paquete para poder organizarlo. Esto es una carpeta que va dentro de src/ o de src/main/java/, en la que introducimos archivos .java con el código de nuestros programas. Paralelamente, una declaración llamada package foo; (donde foo es el nombre del paquete) va en la parte superior de cada uno de los archivos Java que estén contenidos en esa misma carpeta.
Sin embargo, Java te permite tener clases fuera de un paquete, si las metes dentro del mismo directorio principal del código fuente de tu aplicación (src/, por ejemplo). En este caso, como no hay paquete, tampoco necesitas agregarle ninguna declaración package. Por ejemplo, el siguiente archivo de un proyecto de NetBeans o de IntelliJ IDEA estaría en la ruta src/Main.java:
public class Main {
public static void main(String[] args) {
// Este podría ser un main de toda la vida.
System.out.println("Hola mundo");
}
}
El paquete anónimo tiene algunas limitaciones. No es posible referenciar las clases de este paquete desde otro paquete, por lo que no puede ser usado como biblioteca de clases que podamos reusar en nuestro programa. Típicamente el paquete anónimo sólo se usa con fines didácticos cuando se está enseñando a programar Java, o para guardar el método Main del programa principal (y aun así, en muchas ocasiones resultará mucho más beneficioso guardar ese Main en un paquete de verdad).
Bueno, pues volviendo al tema, la propuesta de Java con esta nueva función sería llevar este mismo concepto a las clases. Si el compilador de Java se encuentra con un archivo .java que no tiene declaración de clases y que forma parte del paquete anónimo, es decir, se encuentra con un archivo que directamente abre con código como public void main() o public Logger loggerInstance;, entonces ese código sería capturado en una clase anónima que no tendría nombre pero que a efectos de la máquina virtual se pueda utilizar como clase principal. Dado que esta clase no tiene nombre, no puede ser referenciada desde ninguna otra parte de la aplicación, así que su único uso útil sería crear métodos main.
Para entender esto, vamos a imaginar el siguiente archivo de código dentro de src/Main.java:
void printHeading() {
System.out.println("== MI APLICACIÓN ==");
}
void main() {
this.printHeading();
var window = new WelcomeWindow();
window.setVisible(true);
}
Este archivo abre directamente con dos funciones: printHeading y main. No hay ninguna clase aquí declarada, de modo que lo que hará el compilador es fabricar una clase anónima con esto:
new Object() {
void printHeading() {
System.out.println("== MI APLICACIÓN ==");
}
void main() {
this.printHeading();
var window = new WelcomeWindow();
window.setVisible(true);
}
};
Y, a continuación, dado que esta clase anónima tiene un método main, podrá ser usada como método principal para iniciar la aplicación.
Esta clase no puede ser referenciada por otras, así que sólo valdría para declarar una clase principal.
Prioridades
Lo último que querría esta nueva función es romper la compatibilidad con todo el código escrito en los últimos 30 años, por lo que siempre va a tener prioridad el comportamiento tradicional. Por ejemplo, si una clase principal tiene un método public static void main(String[] args), este sería el que se utilizase siempre. Sólamente en caso de que no exista este método main es que se tirará de alguno de los nuevos, y la precedencia sería: métodos main estáticos > métodos main no estáticos, y a la vez métodos main con parámetro String[] args > métodos main sin parámetro String[] args.
Esto es provisional
Recordemos una vez más que en el momento de escribir esto y de grabar el vídeo que acompaña a este artículo, no se ha implementado todavía en el Early Access del JDK 21 el código que permitiría usar en modo previa estas funciones. De modo que hay que tratar esto como provisional. A medida que esta función esté disponible en las EA del OpenJDK 21 podremos saber más sobre esta función. Hasta entonces, todo son suposiciones.
Conclusiones: ¿por qué este cambio?
Una de las principales razones que ha propiciado este cambio es reducir la curva de aprendizaje de Java para personas nuevas que lleguen al lenguaje. Java se sigue usando para enseñar orientación a objetos mucho debido a que se amolda bastante bien a sus principios. Sin embargo, una de las desventajas de Java es que por su insistencia tradicional en usar clase para todo y métodos estáticos, a veces esa verbosidad hace que sea complicado explicar Java porque estás introduciendo palabras que quieres definir luego como public, class o static. Con este cambio, lo que se busca es hacer más fácil de aprender Java porque ahora para crear un hola mundo sólo hay que saber qué es un método, en este caso, el main.
// El archivo empieza aquí.
void main() {
System.out.println("Hola mundo");
}
Podríamos argumentar que System.out.println es igualmente un poco verboso y que tal vez haya que aclarar qué quiere decir System y System.out en este contexto, pero hemos quitado complejidad visual.
Otra razón podría ser, simplemente hacer el lenguaje más competitivo en un mundo que tiene tantos lenguajes. Evidentemente, a las empresas que llevan Java les interesa que siga siendo un lenguaje relevante para poder vender sus licencias. Por lo tanto, tiene sentido que quieran mantener el lenguaje actualizado, competitivo y de moda, porque cuanta más gente deje de apostar por Java y se pase a otro tipo de tecnologías menos verbosas, menos presencia de mercado podría tener. Sí, me estoy refiriendo a Oracle.
No se trata de un cambio que le guste a todo el mundo, en parte debido al sondeo que he podido ver en los comentarios que acompañan al vídeo que he publicado junto a este artículo. Sin embargo, no cabe duda que se tratan de unos cambios interesantes los que estamos viendo últimamente en el lenguaje de programación. La sintaxis de Java parecía una cosa inalterable y permanente, pero últimamente se están viendo cambios en el lenguaje de programación que prueban que la sintaxis de Java también puede evolucionar y mejorar con el paso del tiempo. Quién sabe lo que nos deparará el futuro.