Módulos en Java

Avanzado

Con el lanzamiento de JDK 9 se agregó una nueva e importante característica a Java llamada módulos. Los módulos le dan una forma de describir las relaciones y dependencias del código que comprende una aplicación. Los módulos también le permiten controlar qué partes de un módulo son accesibles para otros módulos y cuáles no. Mediante el uso de módulos, puede crear programas más confiables y escalables.

Como regla general, los módulos son más útiles para las aplicaciones de gran tamaño porque ayudan a reducir la complejidad de gestión a menudo asociada con un gran sistema de software. Sin embargo, los pequeños programas también se benefician de los módulos porque la biblioteca API de Java ahora se ha organizado en módulos. Por lo tanto, ahora es posible especificar qué partes de la API requieren tu programa y cuáles no. Esto hace posible implementar programas con un espacio de tiempo de ejecución más pequeño, lo cual es especialmente importante cuando se crean códigos para dispositivos pequeños, como los que se pretende que formen parte de Internet de las Cosas (IoT).

La compatibilidad con los módulos se proporciona mediante elementos del lenguaje, incluidas nuevas palabras clave, y mediante mejoras en javac, java y otras herramientas JDK. Además, se han introducido nuevas herramientas y formatos de archivo. Como resultado, el JDK y el sistema de tiempo de ejecución proporcionado por JDK 9 se han actualizado sustancialmente a módulos de soporte. En resumen, los módulos constituyen una importante adición y evolución del lenguaje Java.

1. Fundamentos del módulo

En su sentido más fundamental, un módulo es una agrupación de paquetes y recursos a los que se puede hacer referencia colectivamente por el nombre del módulo. Una declaración de módulo especifica el nombre de un módulo y define la relación que un módulo y sus paquetes tienen con otros módulos. Las declaraciones de módulo son declaraciones de programa en un archivo fuente de Java y son compatibles con varias palabras clave nuevas relacionadas con el módulo. Ellas se muestran aquí:

Palabras claves reservadas (Módulos) en Java
openmodulerequirestransitive
exportsopenstouses
provideswith

Es importante comprender que estas palabras clave se reconocen como palabras clave solo en el contexto de una declaración de módulo. De lo contrario, se interpretan como identificadores en otras situaciones. Por lo tanto, la palabra clave module podría, por ejemplo, también ser utilizado como un nombre de parámetro, pero ese uso ciertamente no se recomienda ahora.

Una declaración de módulo está contenida en un archivo llamado module-info.java. Por lo tanto, un módulo se define en un archivo fuente Java. Este archivo es compilado por javac en un archivo de clase y se conoce como un descriptor de módulo (module descriptor). El archivo module-info.java debe contener solo una definición de módulo. No es un archivo de propósito general.

Una declaración de módulo comienza con la palabra clave module. Aquí está su forma general:

module nombreModulo {
// definición del módulo
}

El nombre del módulo está especificado por nombreModulo, que debe ser un identificador de Java válido o una secuencia de identificadores separados por puntos. La definición del módulo se especifica dentro de las llaves. Aunque una definición de módulo puede estar vacía (lo que da como resultado una declaración que simplemente nombra el módulo), típicamente especifica una o más cláusulas que definen las características del módulo.

2. Un ejemplo de módulo simple

En la base de las capacidades de un módulo hay dos características clave.

  • El primero es la capacidad de un módulo para especificar que requiere otro módulo. En otras palabras, un módulo puede especificar que depende de otro. Una relación de dependencia se especifica mediante el uso de una instrucción require. De forma predeterminada, la presencia del módulo requerido se comprueba tanto en tiempo de compilación como en tiempo de ejecución.
  • La segunda característica clave es la capacidad de un módulo para controlar cuáles de sus paquetes, si es que tiene alguno, son accesibles por otro módulo. Esto se logra mediante el uso de la palabra clave exports. Los tipos public y protected dentro de un paquete solo son accesibles a otros módulos si se exportan explícitamente. Aquí desarrollaremos un ejemplo que presenta estas dos características.

El siguiente ejemplo crea una aplicación modular que demuestra algunas funciones matemáticas simples. Aunque esta aplicación es deliberadamente muy pequeña, ilustra los conceptos básicos y los procedimientos necesarios para crear, compilar y ejecutar código basado en módulos. Además, el enfoque general que se muestra aquí también se aplica a las aplicaciones más grandes del mundo real. Se recomienda enfáticamente que trabaje con el ejemplo en su computadora, siguiendo cuidadosamente cada paso.

2.1. Preparando la aplicación

La aplicación define dos módulos. El primer módulo se llama appinicio. Contiene un paquete llamado appinicio.midemoappmod que define el punto de entrada de la aplicación en una clase llamada MiAppModDemo. Por lo tanto, MiAppModDemo contiene el método main() de la aplicación. El segundo módulo se llama appfuncs. Contiene un paquete llamado appfuncs.funcsimples que incluye la clase FuncsMateSimples. Esta clase define tres métodos estáticos que implementan algunas funciones matemáticas simples. Toda la aplicación estará contenida en un árbol de directorios que comienza en miappmodulo.

Antes de continuar, unas palabras sobre los nombres de los módulos son apropiadas. Primero, en los ejemplos que siguen, el nombre de un módulo (como appfuncs) es el prefijo del nombre de un paquete que contiene (como appfuncs.funcsimples). Esto no es obligatorio, pero se usa aquí como una forma de indicar claramente a qué módulo pertenece un paquete. En general, al aprender y experimentar con módulos, los nombres cortos y simples, como los que se utilizan aquí, son útiles, y puede usar cualquier tipo de nombre conveniente que desee.

En el momento de escribir esto, la forma sugerida para lograr esto es usar el método de nombre de dominio inverso. Con este método, el nombre de dominio inverso del dominio que “posee” el proyecto se usa como un prefijo para el módulo. Por ejemplo, un proyecto asociado con javadesdecero.es usaría es.javadesdecero como el prefijo del módulo. (Lo mismo ocurre con los nombres de los paquetes.) Debido a que los módulos son una nueva adición a Java, las convenciones de nombres pueden evolucionar con el tiempo. Deberá verificar la documentación de Java para conocer las recomendaciones actuales.

2.2. Estructura de la aplicación

Comencemos ahora. Comience por crear los directorios de códigos fuente necesarios siguiendo estos pasos:

  1. Crea un directorio llamado miappmodulo. Este es el directorio de nivel superior para toda la aplicación.
  2. En miappmodulo, crea un subdirectorio llamado appsrc. Este es el directorio de nivel superior para el código fuente de la aplicación.
  3. En appsrc, cree un subdirectorio appinicio. En este directorio, cree un subdirectorio también llamado appinicio. Bajo este directorio, crea el directorio midemoappmod. Por lo tanto, comenzando con appsrc, habrá creado este árbol: appsrc\appinicio\appinicio\midemoappmod
  4. También en appsrc, cree el subdirectorio appfuncs. Bajo este directorio, cree un subdirectorio también llamado appfuncs. Bajo este directorio, crea el directorio llamado funcsimples. Por lo tanto, comenzando con appsrc, habrá creado este árbol: appsrc\appfuncs\appfuncs\funcsimples

Su árbol de directorios debería verse como el que se muestra aquí:

Módulos en Java - árbol de directorios
Módulos en Java – árbol de directorios

Después de configurar estos directorios, puede crear los archivos fuente de la aplicación.

2.3. Primer archivo

Este ejemplo usará cuatro archivos fuente. Dos son los archivos fuente que definen la aplicación. El primero es FuncsMateSimples.java, que se muestra a continuación. Observe que FuncsMateSimples.java está empaquetado en appfuncs.funcsimples.

package appfuncs.funcsimples;
public class FuncsMateSimples{

    // Determina si a es divisor de b.
    public static boolean esDivisor(int a, int b){
          if ((b%a)==0) return true;
          return false;
    }

    // Devuelve el divisor positivo más pequeño que a y b tienen en común.
    public static int divPeq(int a, int b){
        a=Math.abs(a);
        b=Math.abs(b);

        int min=a < b ? a : b;

        for (int i=2; i<=min/2;i++){
            if (esDivisor(i,a) && esDivisor(i,b))
                return i;
        }

        return 1;
    }

    // Devuelve el mayor factor positivo que a y b tienen en común.
    public static int divGra(int a, int b){
        a=Math.abs(a);
        b=Math.abs(b);

        int min=a < b ? a : b;

        for (int i=min/2;i>=2;i--){
            if (esDivisor(i,a) && esDivisor(i,b))
                return i;
        }

        return 1;
    }

}

FuncsMateSimples define tres funciones matemáticas estáticas (static) simples. El primero, esDivisor(), devuelve true si a es un divisor de b. El método divPeq() devuelve el divisor más pequeño común entre a y b. En otras palabras, devuelve el divisor menos común de a y b. El método divGra() devuelve el mayor divisor común de a y b. En ambos casos, se devuelve 1 si no se encuentran divisores comunes. Este archivo debe colocarse en el siguiente directorio:

appsrc\appfuncs\appfuncs\funcsimples

Este es el directorio de package appfuncs.funcsimples;

2.4. Segundo archivo

El segundo archivo fuente es MiAppModDemo.java, que se muestra a continuación. Utiliza los métodos en FuncsMateSimples. Observe que está empaquetado en appinicio\midemoappmod. También tenga en cuenta que importa la clase FuncsMateSimples porque depende de FuncsMateSimples para su funcionamiento.

// Demuestración de una aplicación simple basada en módulos.

package appinicio.midemoappmod;
import appfuncs.funcsimples.FuncsMateSimples;

public class MiAppModDemo {
    public static void main(String[] args) {
        if(FuncsMateSimples.esDivisor(2,10));
        System.out.println("2 es divisor de 10");

        System.out.println("El divisor positivo más pequeño entre 33 y 99 es: "
                            +FuncsMateSimples.divPeq(33,99));
        System.out.println("El divisor positivo más grande entre 33 y 99 es: "
                +FuncsMateSimples.divGra(33,99));
    }

}

Este archivo debe colocarse en el siguiente directorio:

appsrc\appinicio\appinicio\midemoappmod

Este es el directorio del package appinicio.midemoappmod;

2.5. Archivos module-info.java

A continuación, deberá agregar archivos module-info.java para cada módulo. Estos archivos contienen las definiciones de los módulos. Primero, agregue este, que define el módulo appfuncs:

// Definición del módulo para el módulo de funciones.
module appfuncs{
    // Exporta los paquetes appfuncs.funcsimples.
    exports appfuncs.funcsimples;
}

Tenga en cuenta que appfuncs exporta el paquete appfuncs.funcsimples, que lo hace accesible a otros módulos. Este archivo debe colocarse en este directorio:

appsrc\appfuncs

Por lo tanto, va en el directorio del módulo appfuncs, que está encima de los directorios del paquete.

Finalmente, agregue el archivo module-info.java para el módulo appinicio. Se muestra aquí. Tenga en cuenta que appinicio requiere el módulo appfuncs.

// Definición del módulo para el módulo de aplicación principal (main).
module appinicio{
    // Requiere el módulo appfuncs.
    requires appfuncs;
}

Este archivo debe colocarse en su directorio de módulos:

appsrc\appinicio

Antes de examinar las declaraciones: require, exports y module más de cerca, primero compilemos y ejecutemos este ejemplo. Asegúrese de haber creado correctamente los directorios e ingresado cada archivo en su directorio apropiado, como se acaba de explicar.

2.6. Compilar y ejecutar el primer ejemplo de módulo

A partir de JDK 9, javac se ha actualizado para admitir módulos. Por lo tanto, como todos los demás programas de Java, los programas basados en módulos se compilan utilizando javac. El proceso es fácil, con la diferencia principal de que generalmente se especificará explícitamente una ruta de módulo. Una ruta de módulo le dice al compilador dónde se ubicarán los archivos compilados. Cuando siga este ejemplo, asegúrese de ejecutar los comandos javac desde el directorio miappmodulo para que las rutas sean correctas. Recuerde que miappmodulo es el directorio de nivel superior para toda la aplicación del módulo.

Para comenzar, compile el archivo FuncsMateSimples.java, usando este comando:

javac -d appmodules/appfuncs appsrc\appfuncs\appfuncs\funcsimples\FuncsMateSimples.java

Recuerde, este comando debe ejecutarse desde el directorio miappmodulo. Observe el uso de la opción -d. Esto le dice a javac dónde colocar el archivo .class de salida. Para los ejemplos de este tema, la parte superior del árbol de directorios para el código compilado es appmodules. Este comando creará automáticamente los directorios del paquete de salida para appfuncs.funcsimples bajo appmodules\appfuncs según sea necesario.

A continuación, aquí está el comando javac que compila el archivo module-info.java para el módulo appfuncs:

javac -d appmodules\appfuncs appsrc\appfuncs\module-info.java
Compilar y ejecutar módulos Java
Compilar y ejecutar módulos Java

Esto coloca el archivo module-info.class en el directorio appmodules\appfuncs.

Aunque el proceso anterior de dos pasos funciona, se mostró principalmente por el bien de la discusión. Por lo general, es más fácil compilar el archivo module-info.java de un módulo y sus archivos fuente en una línea de comando. Aquí, los dos comandos anteriores javac se combinan en uno:

javac -d appmodules/appfuncs appsrc\appfuncs\module-info.java 
appsrc\appfuncs\appfuncs\funcsimples\FuncsMateSimples.java

En este caso, cada archivo compilado se coloca en su módulo o directorio de paquetes apropiado. Ahora, compile los archivos module-info.java y MiAppModDemo.java para el módulo appinicio, usando este comando:

javac --module-path appmodules -d appmodules/appinicio appsrc\appinicio\module-info.java 
appsrc\appinicio\appinicio\midemoappmod\MiAppModDemo.java

2.7. Comandos asociados a module

Observe la opción –module-path. Especifica la ruta del módulo, que es la ruta en la que el compilador buscará los módulos definidos por el usuario requeridos por el archivo module-info.java. En este caso, buscará el módulo appfuncs porque lo necesita el módulo appinicio. Además, observe que especifica el directorio de salida como appmodules/appinicio. Esto significa que el archivo module-info.class estará en el directorio del módulo appmodules/appinicio y MiAppModDemo.class estará en el directorio del paquete appmodules\appinicio\appinicio\midemoappmod.

Comando --module-path en Java para módulos
Comando –module-path en Java

Una vez que haya completado la compilación, puede ejecutar la aplicación con este comando java:

java --module-path appmodules -m appinicio/appinicio.midemoappmod.MiAppModDemo

Aquí, la opción –module-path especifica la ruta a los módulos de la aplicación. Como se mencionó, appmodules es el directorio en la parte superior del árbol de módulos compilados. La opción -m especifica la clase que contiene el punto de entrada de la aplicación y, en este caso, el nombre de la clase que contiene el método main(). Cuando ejecutas el programa, verás el siguiente resultado:

2 es divisor de 10
El divisor positivo más pequeño entre 33 y 99 es: 3
El divisor positivo más grande entre 33 y 99 es: 11
Ejemplo module en Java
Salida de ejemplo de módulos en Java

3. Opciones de requires y exports

El ejemplo anterior basado en módulos se basa en las dos características fundamentales del sistema de módulos: la capacidad de especificar una dependencia y la capacidad de satisfacer esa dependencia. Estas capacidades se especifican mediante el uso de las declaraciones requires y exports dentro de una declaración module. Cada uno merece un examen más detallado en este momento.

Aquí está la forma de la declaración requires usada en el ejemplo:

requires nombreModulo;

Aquí, nombreModulo especifica el nombre de un módulo requerido por el módulo en el que se produce la instrucción require. Esto significa que el módulo requerido debe estar presente para que el módulo actual pueda compilarse. En el lenguaje de los módulos, se dice que el módulo actual lee el módulo especificado en la instrucción require. En general, la declaración de requisitos le proporciona una forma de garantizar que su programa tenga acceso a los módulos que necesita.

Aquí está la forma general de la declaración exports utilizada en el ejemplo:

exports nombrePaquete;

Aquí, nombrePaquete especifica el nombre del paquete que se exporta por el módulo en el que se produce esta declaración. Cuando un módulo exporta un paquete, hace que todos los tipos públicos y protegidos en el paquete sean accesibles para otros módulos. Además, los miembros públicos y protegidos de esos tipos también son accesibles. Sin embargo, si un paquete dentro de un módulo no se exporta, entonces es privado para ese módulo, incluidos todos sus tipos públicos.

Por ejemplo, a pesar de que una clase se declara como public dentro de un paquete, si ese paquete no es exportado explícitamente por una declaración exports, entonces esa clase no es accesible para otros módulos. Es importante comprender que los tipos public y protected de un paquete, ya sean exportados o no, siempre son accesibles dentro del módulo de ese paquete. La declaración exports simplemente los hace accesibles a módulos externos.

Es importante enfatizar que las sentencias requires y exports deben ocurrir sólo dentro de una declaración mudule. Además, una declaración mudule debe aparecer por sí misma en un archivo llamado module-info.java

Continua el aprendizaje de módulo con la parte 2.

Módulos en Java
  • Fundamentos y Ejemplo
Sending
User Review
5 (1 vote)

Sobre el Autor:

Hey hola! Yo soy Alex Walton y tengo el placer de compartir conocimientos hacía ti sobre el tema de Programación en Java, desde cero, Online y Gratis.

Deja una Respuesta

*

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.