Comment fonctionne la déclaration de module en Java?

Maintenant que nous avons créé des modules avec succès, nous pouvons approfondir la déclaration de module. Dans ces sections, nous examinerons exports, requires, et opens. Dans la section suivante sur les services, nous explorerons provides et uses. C’est le bon moment pour mentionner que ces directives peuvent apparaître dans n’importe quel ordre dans la déclaration du module.

Exportation d’un Package

Nous avons déjà vu comment exports packageName exporte un package vers d’autres modules. Il est également possible d’exporter un package vers un module spécifique. Supposons que le zoo décide que seuls les membres du personnel devraient avoir accès aux conférences. Nous pourrions mettre à jour la déclaration du module comme suit :

module zoo.animal.talks {
    exports zoo.animal.talks.content to zoo.staff;
    exports zoo.animal.talks.media;
    exports zoo.animal.talks.schedule;
    requires zoo.animal.feeding;
    requires zoo.animal.care;
}

Du point de vue du module zoo.staff, rien n’a changé. Cependant, aucun autre module ne serait autorisé à accéder à ce package.

Vous avez peut-être remarqué qu’aucun de nos autres modules ne requiert zoo.animal.talks en premier lieu. Cependant, nous ne savons pas quels autres modules existeront à l’avenir. Il est important de considérer l’utilisation future lors de la conception des modules. Puisque nous voulons que seul ce module ait accès, nous n’autorisons l’accès que pour ce module.

Types Exportés

Nous avons parlé d’exporter un package. Mais qu’est-ce que cela signifie exactement ?

Toutes les classes public, interfaces, enums et records sont exportés. De plus, tous les champs et méthodes public et protected dans ces fichiers sont visibles.

Les champs et méthodes qui sont private ne sont pas visibles car ils ne sont pas accessibles en dehors de la classe. De même, les champs et méthodes de package ne sont pas visibles car ils ne sont pas accessibles en dehors du package.

La directive exports nous donne essentiellement plus de niveaux de contrôle d’accès. Le Tableau 12.3 liste les options complètes de contrôle d’accès.

NiveauDans le code du moduleEn dehors du module
privateDisponible uniquement dans la classePas d’accès
PackageDisponible uniquement dans le packagePas d’accès
protectedDisponible uniquement dans le package ou les sous-classesAccessible aux sous-classes uniquement si le package est exporté
publicDisponible pour toutes les classesAccessible uniquement si le package est exporté

Requérir un Module de Manière Transitive

Comme vous l’avez vu plus tôt dans ce chapitre, requires moduleName spécifie que le module actuel dépend de moduleName. Il existe également requires transitive moduleName, qui signifie que tout module qui requiert ce module dépendra également de moduleName.

Voyons un exemple. La Figure 12.12 montre les modules avec des lignes pointillées pour les relations redondantes et des lignes continues pour les relations spécifiées dans le module-info. Cela montre comment les relations de module apparaîtraient si nous n’utilisions que des dépendances transitives.

Par exemple, zoo.animal.talks dépend de zoo.animal.care, qui dépend de zoo.animal.feeding. Cela signifie que la flèche entre zoo.animal.talks et zoo.animal.feeding n’apparaît plus dans la Figure 12.12.

Examinons maintenant les quatre déclarations de module. Le premier module reste inchangé. Nous exportons un package vers tous les packages qui utilisent le module.

module zoo.animal.feeding {
    exports zoo.animal.feeding;
}

Le module zoo.animal.care est la première opportunité d’améliorer les choses. Plutôt que de forcer tous les modules restants à spécifier explicitement zoo.animal.feeding, le code utilise requires transitive.

module zoo.animal.care {
    exports zoo.animal.care.medical;
    requires transitive zoo.animal.feeding;
}

Dans le module zoo.animal.talks, nous faisons un changement similaire et ne forçons pas les autres modules à spécifier zoo.animal.care. Nous n’avons également plus besoin de spécifier zoo.animal.feeding, donc cette ligne est commentée.

module zoo.animal.talks {
    exports zoo.animal.talks.content to zoo.staff;
    exports zoo.animal.talks.media;
    exports zoo.animal.talks.schedule;
    // plus nécessaire requires zoo.animal.feeding;
    // plus nécessaire requires zoo.animal.care;
    requires transitive zoo.animal.care;
}

Enfin, dans le module zoo.staff, nous pouvons nous débarrasser de deux instructions requires.

module zoo.staff {
    // plus nécessaire requires zoo.animal.feeding;
    // plus nécessaire requires zoo.animal.care;
    requires zoo.animal.talks;
}

Plus vous avez de modules, plus les avantages du composé requires transitive sont importants. C’est également plus pratique pour l’appelant. Si vous essayiez de travailler avec ce zoo, vous pourriez simplement demander zoo.staff et avoir les dépendances restantes automatiquement déduites.

Effets de requires transitive

Étant donné nos nouvelles déclarations de module, et en utilisant la Figure 12.12, quel est l’effet de l’application du modificateur transitive à l’instruction requires dans notre module zoo.animal.care ? L’application des modificateurs transitifs a les effets suivants :

  • Le module zoo.animal.talks peut éventuellement déclarer qu’il requiert le module zoo.animal.feeding, mais ce n’est pas obligatoire.
  • Le module zoo.animal.care ne peut pas être compilé ou exécuté sans accès au module zoo.animal.feeding.
  • Le module zoo.animal.talks ne peut pas être compilé ou exécuté sans accès au module zoo.animal.feeding.

Ces règles s’appliquent même si les modules zoo.animal.care et zoo.animal.talks ne référencent pas explicitement des packages dans le module zoo.animal.feeding. D’autre part, sans le modificateur transitive dans notre déclaration de module de zoo.animal.care, les autres modules devraient utiliser explicitement requires pour référencer des packages dans le module zoo.animal.feeding.

Instructions requires dupliquées

Un endroit où vous pourriez être piégé est en mélangeant requires et requires transitive. Pouvez-vous penser à une raison pour laquelle ce code ne compile pas ?

module bad.module {
    requires zoo.animal.talks;
    requires transitive zoo.animal.talks;
}

Java ne vous permet pas de répéter le même module dans une clause requires. C’est redondant et probablement une erreur de codage. Gardez à l’esprit que requires transitive est comme requires plus un comportement supplémentaire.

Ouverture d’un Package

Java permet aux appelants d’inspecter et d’appeler du code au moment de l’exécution avec une technique appelée réflexion. C’est une approche puissante qui permet d’appeler du code qui pourrait ne pas être disponible au moment de la compilation. Elle peut même être utilisée pour contourner le contrôle d’accès !

La directive opens est utilisée pour activer la réflexion d’un package dans un module. Vous devez seulement être conscient que la directive opens existe plutôt que de la comprendre en détail.

Comme la réflexion peut être dangereuse, le système de modules oblige les développeurs à autoriser explicitement la réflexion dans la déclaration du module s’ils veulent que les modules appelants soient autorisés à l’utiliser. Voici comment activer la réflexion pour deux packages dans le module zoo.animal.talks :

module zoo.animal.talks {
    opens zoo.animal.talks.schedule;
    opens zoo.animal.talks.media to zoo.staff;
}

Le premier exemple permet à tout module utilisant celui-ci d’utiliser la réflexion. Le deuxième exemple ne donne ce privilège qu’au module zoo.staff. Il existe deux autres directives que vous devez connaître : provides et uses, qui sont couvertes dans la section suivante.

Scénario du Monde Réel

Ouverture d’un Module Entier

Dans l’exemple précédent, nous avons ouvert deux packages dans le module zoo.animal.talks, mais supposons que nous voulions plutôt ouvrir tous les packages pour la réflexion. Pas de problème. Nous pouvons utiliser le modificateur de module open, plutôt que la directive opens (notez la différence du s) :

open module zoo.animal.talks {
}

Avec ce modificateur de module, Java sait que nous voulons que tous les packages du module soient ouverts. Que se passe-t-il si vous appliquez les deux ensemble ?

open module zoo.animal.talks {
    opens zoo.animal.talks.schedule; // NE COMPILE PAS
}

Cela ne compile pas car un modificateur qui utilise le modificateur open n’est pas autorisé à utiliser la directive opens. Après tout, les packages sont déjà ouverts !