Partager via


Passer de Java 8 à Java 11

Il n’existe pas de solution universelle pour effectuer la transition du code de Java 8 vers Java 11. Pour une application stratégique, la transition de Java 8 à Java 11 peut représenter un travail considérable. Les problèmes potentiels peuvent être de différentes natures : API supprimée, packages dépréciés, utilisation de l’API interne, modifications apportées aux chargeurs de classes et au nettoyage de la mémoire, etc.

En général, les approches consistent à tenter d’exécuter le code sur Java 11 sans recompiler ou à compiler d’abord avec le JDK 11. Si vous avez pour objectif de disposer d’une application opérationnelle aussi rapidement que possible, la meilleure approche consiste généralement à juste exécuter le code sur Java 11. Pour une bibliothèque, l’objectif est de publier un artefact compilé et testé avec JDK 11.

L’effort consacré à la transition vers Java 11 est payant. Depuis Java 8, de nouvelles fonctionnalités ont été ajoutées et des améliorations ont été apportées. Ces fonctionnalités et améliorations optimisent le démarrage, les performances et l’utilisation de la mémoire. De plus, elles assurent une meilleure intégration avec les conteneurs. Par ailleurs, les ajouts et modifications apportés à l’API améliorent la productivité des développeurs.

Ce document aborde les outils d’inspection du code. Il aborde également les problèmes que vous pouvez rencontrer et les recommandations de résolution. Nous vous recommandons aussi de consulter d’autres guides, notamment Oracle JDK Migration Guide. Le présent guide n’explique pas comment rendre le code existant modulaire.

Boîte à outils

Java 11 offre deux outils utiles pour la détection des problèmes potentiels : jdeprscan et jdeps. Vous pouvez exécuter ces outils sur des fichiers de classe ou jar existants. Vous pouvez évaluer l’effort de transition sans avoir à recompiler.

jdeprscan identifie si une API dépréciée ou supprimée est utilisée. L’utilisation d’une API dépréciée n’est pas un problème bloquant, mais c’est un aspect à examiner. Existe-t-il un fichier jar mis à jour ? Avez-vous besoin de signaler un problème pour gérer l’utilisation d’une API dépréciée ? L’utilisation d’une API supprimée est un problème bloquant que vous devez résoudre avant d’essayer d’exécuter le code sur Java 11.

jdeps est un analyseur de dépendances de classes Java. Quand vous l’utilisez avec l’option --jdk-internals, jdeps indique quelle classe dépend de quelle API interne. Vous pouvez continuer à utiliser une API interne dans Java 11, mais son remplacement doit être considéré comme une priorité. La page wiki OpenJDK Java Dependency Analysis Tool (Outil d’analyse des dépendances Java) offre des recommandations quant au remplacement de certaines API internes courantes du JDK.

Il existe des plug-ins jdeps et jdeprscan pour Gradle et Maven. Nous vous recommandons d’ajouter ces outils à vos scripts de build.

Le compilateur Java javac représente lui-même un autre outil de votre boîte à outils. Les erreurs et avertissements émis par jdeprscan et jdeps sont indiqués par le compilateur. L’utilisation de jdeprscan et jdeps vous offre notamment l’avantage de pouvoir exécuter ces outils sur des fichiers de classe et jar, y compris des bibliothèques tierces.

En revanche, jdeprscan et jdeps ne peuvent pas fournir d’avertissement quant à l’utilisation de la réflexion pour l’accès à une API encapsulée. L’accès réfléchissant est vérifié au moment de l’exécution. Au final, vous devez exécuter le code sur Java 11 pour vérifier ce point avec certitude.

Utilisation de jdeprscan

Le moyen le plus simple d’utiliser jdeprscan consiste à lui attribuer un fichier jar à partir d’une build existante. Vous pouvez également lui attribuer un répertoire tel que le répertoire de sortie du compilateur ou un nom de classe individuel. Utilisez l’option --release 11 pour obtenir la liste d’API dépréciées la plus complète. Si vous souhaitez rechercher les API dépréciées par ordre de priorité, rétablissez le paramètre sur --release 8. Une API dépréciée dans Java 8 est susceptible d’être supprimée plus tôt qu’une API dépréciée plus récemment.

jdeprscan --release 11 my-application.jar

L’outil jdeprscan génère un message d’erreur s’il ne parvient pas à résoudre une classe dépendante. Par exemple : error: cannot find class org/apache/logging/log4j/Logger. Il est recommandé d’ajouter les classes dépendantes à --class-path ou d’utiliser le chemin de classe de l’application. Cependant, l’outil poursuit l’analyse sans le chemin de classe. L’argument est --class-path. Aucune autre variante de l’argument class-path ne fonctionnera.

jdeprscan --release 11 --class-path log4j-api-2.13.0.jar my-application.jar
error: cannot find class sun/misc/BASE64Encoder
class com/company/Util uses deprecated method java/lang/Double::<init>(D)V

Cette sortie nous indique que la classe com.company.Util appelle un constructeur déprécié de la classe java.lang.Double. Le javadoc recommande l’API à utiliser à la place d’une API dépréciée. Vous ne parviendrez pas à résoudre l’erreur error: cannot find class sun/misc/BASE64Encoder, car il s’agit d’une API qui a été supprimée. Depuis Java 8, java.util.Base64 doit être utilisé.

Exécutez jdeprscan --release 11 --list pour avoir une idée des API qui ont été dépréciées depuis Java 8. Pour obtenir une liste des API supprimées, exécutez jdeprscan --release 11 --list --for-removal.

Utilisation de jdeps

Utilisez jdeps avec l’option --jdk-internals pour rechercher des dépendances à une API interne du JDK. L’option de ligne de commande --multi-release 11 est nécessaire pour cet exemple, car log4j-core-2.13.0.jar est un fichier jar multiversion. Avec cette option, jdeps signalera tout fichier jar multiversion détecté. L’option spécifie la version des fichiers de classe à inspecter.

jdeps --jdk-internals --multi-release 11 --class-path log4j-core-2.13.0.jar my-application.jar
Util.class -> JDK removed internal API
Util.class -> jdk.base
Util.class -> jdk.unsupported
   com.company.Util        -> sun.misc.BASE64Encoder        JDK internal API (JDK removed internal API)
   com.company.Util        -> sun.misc.Unsafe               JDK internal API (jdk.unsupported)
   com.company.Util        -> sun.nio.ch.Util               JDK internal API (java.base)

Warning: JDK internal APIs are unsupported and private to JDK implementation that are
subject to be removed or changed incompatibly and could break your application.
Please modify your code to eliminate dependence on any JDK internal APIs.
For the most recent update on JDK internal API replacements, please check:
https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool

JDK Internal API                         Suggested Replacement
----------------                         ---------------------
sun.misc.BASE64Encoder                   Use java.util.Base64 @since 1.8
sun.misc.Unsafe                          See http://openjdk.java.net/jeps/260   

La sortie fournit des conseils intéressants sur l’élimination de l’utilisation d’une API interne du JDK. L’API de remplacement est suggérée dans la mesure du possible. Le nom du module dans lequel le package est encapsulé est indiqué entre parenthèses. Le nom du module peut être utilisé avec --add-exports ou --add-opens s’il est nécessaire de casser l’encapsulation de manière explicite.

L’utilisation de sun.misc.BASE64Encoder ou sun.misc.BASE64Decoder entraînera une erreur java.lang.NoClassDefFoundError dans Java 11. Le code qui utilise ces API doit être modifié pour utiliser java.util.Base64.

Essayez d’éliminer l’utilisation de toute API provenant du module jdk.unsupported. Les API de remplacement suggérées pour les API de ce module sont référencées dans le document JEP (JDK Enhancement Proposal) 260. Pour résumer, le document JEP 260 indique que l’utilisation d’une API interne sera prise en charge jusqu’à ce qu’une API de remplacement soit disponible. Même si votre code peut utiliser une API interne du JDK, il continue à s’exécuter au moins pendant un certain temps. Examinez le document JEP 260, car il suggère des API de remplacement pour certaines API internes. Des descripteurs de variables peuvent être utilisés à la place de certaines API sun.misc.Unsafe, par exemple.

Les fonctionnalités de jdeps ne se limitent pas à l’analyse de l’utilisation d’API internes du JDK. Cet outil est également utile pour l’analyse des dépendances et la génération de fichiers module-info. Pour plus d’informations, consultez cette documentation.

Utilisation de javac

La compilation avec le JDK 11 nécessite la mise à jour des scripts de build, outils, frameworks de test et bibliothèques incluses. Utilisez l’option -Xlint:unchecked avec javac pour obtenir des informations sur l’utilisation d’API internes du JDK et d’autres avertissements. Il peut également être nécessaire d’utiliser --add-opens ou --add-reads pour exposer les packages encapsulés au compilateur (consultez le document JEP 261).

Les bibliothèques peuvent considérer l’empaquetage comme fichier jar multiversion. Les fichiers jar multiversions vous permettent de prendre en charge les runtimes Java 8 et Java 11 à partir du même fichier jar. Ils compliquent cependant la build. Ce document n’explique pas comment générer les fichiers jar multiversions.

Exécution sur Java 11

La plupart des applications doivent pouvoir s’exécuter sur Java 11 sans modification. Commencez par essayer d’exécuter l’application sur Java 11 sans recompiler le code. Cette simple exécution vous permettra d’observer les avertissements et erreurs pouvant survenir. Cette approche vous permet
d’assurer plus rapidement l’exécution d’une application sur Java 11 en vous concentrant sur les tâches minimales nécessaires.

Vous pouvez résoudre la plupart des problèmes susceptibles de se présenter sans avoir à recompiler le code. Si un problème doit être corrigé dans le code, corrigez-le, puis poursuivez la compilation avec le JDK 8. Si possible, faites en sorte que l’application s’exécute avec java version 11 avant de compiler avec le JDK 11.

Vérifier les options de ligne de commande

Avant de lancer l’exécution sur Java 11, effectuez une analyse rapide des options de ligne de commande. Les options qui ont été supprimées entraînent la fermeture de la Machine virtuelle Java (JVM). Cette vérification est particulièrement importante si vous utilisez les options de journalisation du nettoyage de la mémoire. En effet, elles ont été considérablement modifiées depuis Java 8. L’outil JaCoLine est très utile pour détecter les problèmes liés aux options de ligne de commande.

Vérifier les bibliothèques tierces

Les bibliothèques tierces que vous ne contrôlez pas constituent une source de problèmes potentielle. Vous pouvez mettre à jour les bibliothèques tierces vers des versions plus récentes de manière proactive. Vous pouvez également voir ce qui se passe quand vous exécutez l’application et mettre à jour uniquement les bibliothèques qui le nécessitent. La mise à jour de l’ensemble des bibliothèques vers une version récente est problématique dans le sens où elle complique la recherche de la cause racine en cas d’erreur dans l’application. L’erreur s’est-elle produite en raison d’une bibliothèque mise à jour ? A-t-elle été provoquée par une modification du runtime ? Si vous mettez à jour uniquement les bibliothèques qui le nécessitent, vous serez confronté à une autre problématique : la résolution pourra impliquer plusieurs itérations.

Dans ce cas, nous vous recommandons d’effectuer le moins de modifications possibles et de mettre à jour les bibliothèques tierces dans le cadre d’un travail distinct. Si vous mettez à jour une bibliothèque tierce, vous souhaiterez probablement disposer de la version la plus récente et la plus performante compatible avec Java 11. Selon l’ancienneté de votre version actuelle, il peut être préférable d’adopter une approche plus prudente et d’effectuer une mise à niveau vers la première version compatible Java à partir de la version 9.

Pour évaluer le fichier jar, vous pouvez non seulement examiner les notes de publication, mais également utiliser jdeps et jdeprscan. Par ailleurs, l’OpenJDK Quality Group gère la page wiki Quality Outreach qui liste l’état de test de nombreux projets de logiciels open source gratuits (FOSS, Free Open Source Software) par rapport aux différentes versions d’OpenJDK.

Définir le nettoyage de la mémoire de façon explicite

Le récupérateur de mémoire (GC, garbage collector) Parallel est utilisé par défaut dans Java 8. Si l’application utilise le GC par défaut, celui-ci doit être défini de façon explicite avec l’option de ligne de commande -XX:+UseParallelGC. Le récupérateur de mémoire par défaut a été modifié dans Java 9 : il s’agit de G1GC (Garbage First Garbage Collector). Pour pouvoir comparer correctement l’exécution d’une application sur Java 8 et Java 11, le paramètre GC doit être le même. Les tests d’exécution avec les paramètres GC doivent être reportés jusqu’à ce que l’application ait été validée sur Java 11.

Définir explicitement les options par défaut

Si vous exécutez l’application sur la machine virtuelle HotSpot, la définition de l’option de ligne de commande -XX:+PrintCommandLineFlags vide les valeurs des options définies par la machine virtuelle, en particulier les valeurs par défaut définies par le GC. Exécutez l’application avec cet indicateur sur Java 8 et utilisez les options affichées lors de l’exécution sur Java 11. Pour l’essentiel, les valeurs par défaut sont identiques entre les versions 8 et 11. Toutefois, l’utilisation des paramètres de la version 8 assure la parité.

Il est recommandé de définir l’option de ligne de commande --illegal-access=warn. Dans Java 11, l’utilisation de la réflexion pour accéder à une API interne du JDK génère un avertissement d’accès réfléchissant illégal. Par défaut, l’avertissement n’est émis que pour le premier accès illégal. Si vous définissez --illegal-access=warn, un avertissement sera généré à chaque accès réfléchissant illégal. Vous identifierez plus de cas d’accès illégal si l’option est définie sur warn. Toutefois, vous obtiendrez également un grand nombre d’avertissements redondants.
Dès lors que l’application s’exécute sur Java 11, définissez --illegal-access=deny pour simuler le comportement futur du runtime Java. À partir de Java 16, la valeur par défaut est --illegal-access=deny.

Précautions relatives aux chargeurs de classes

Dans Java 8, vous pouvez caster le chargeur de classe système en URLClassLoader. Cette opération est généralement effectuée par les applications et bibliothèques ayant l’intention d’injecter des classes dans le chemin de classe au moment de l’exécution. La hiérarchie des chargeurs de classes a changé dans Java 11. Le chargeur de classe système (également appelé chargeur de classe d’application) est désormais une classe interne. Si vous le castez en URLClassLoader, l’exception ClassCastException est levée au moment de l’exécution. Java 11 n’a pas d’API permettant d’étendre de façon dynamique le chemin de classe au moment de l’exécution, mais cette extension peut être effectuée par réflexion, avec cependant les inconvénients inhérents à l’utilisation d’une API interne.

Dans Java 11, le chargeur de classe de démarrage charge uniquement les modules principaux. Si vous créez un chargeur de classe avec un parent null, il est possible qu’il ne trouve pas toutes les classes de plateforme. Dans Java 11, dans ce cas, vous devez passer ClassLoader.getPlatformClassLoader() au lieu de null comme chargeur de classe parent.

Modification des données de paramètres régionaux

La source par défaut des données de paramètres régionaux dans Java 11 a été modifiée avec le document JEP 252. Il s’agit désormais du référentiel de données de paramètres régionaux commun du consortium Unicode. Ceci peut avoir un impact sur la mise en forme localisée. Si nécessaire, définissez la propriété système java.locale.providers=COMPAT,SPI pour revenir au comportement de Java 8 vis-à-vis des paramètres régionaux.

Problèmes potentiels

Voici quelques-uns des problèmes courants que vous pouvez rencontrer. Pour plus d’informations sur ces problèmes, suivez les liens fournis.

Options non reconnues

Si une option de ligne de commande a été supprimée, l’application affiche le message Unrecognized option: ou Unrecognized VM option, suivi du nom de l’option concernée. Une option non reconnue entraîne la fermeture de la machine virtuelle. Les options qui ont été dépréciées, mais qui ne sont pas supprimées, génèrent un avertissement de la machine virtuelle (VM Warning).

En général, aucune option de remplacement n’est disponible pour les options qui ont été supprimées. Le seul recours consiste à supprimer l’option de la ligne de commande. Les options de journalisation du nettoyage de la mémoire font exception à cette règle. Cette journalisation a été réimplémentée dans Java 9 pour utiliser le framework de journalisation JVM unifiée. Reportez-vous au tableau 2-2 « Mapping Legacy Garbage Collection Logging Flags to the Xlog Configuration » (Mappage des indicateurs de journalisation du nettoyage de la mémoire hérités à la configuration Xlog) de la section Enable Logging with the JVM Unified Logging Framework du document de référence sur les outils Java SE 11.

Avertissements de la machine virtuelle

L’utilisation d’options dépréciées génère un avertissement. Une option dépréciée est une option qui a été remplacée ou qui n’est plus utile. Tout comme les options supprimées, ces options doivent être supprimées de la ligne de commande. L’avertissement VM Warning: Option <option> was deprecated signifie que la prise en charge de l’option est toujours assurée mais qu’elle est susceptible de prendre fin. Une option qui n’est plus prise en charge générera l’avertissement VM Warning: Ignoring option. Les options qui ne sont plus prises en charge n’ont aucun effet sur le runtime.

La page web VM Options Explorer (Explorateur d’options de machine virtuelle) fournit la liste complète des options qui ont été ajoutées à Java ou qui en ont été supprimées depuis le JDK 7.

Erreur : Could not create the Java Virtual Machine (Impossible de créer la Machine virtuelle Java)

Ce message d’erreur s’affiche quand la Machine virtuelle Java rencontre une option non reconnue.

AVERTISSEMENT : An illegal reflective access operation has occurred (AVERTISSEMENT : une opération d’accès réfléchissant illégale s’est produite)

Quand le code Java utilise la réflexion pour accéder à une API interne du JDK, le runtime émet un avertissement d’accès réfléchissant illégal.

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by my.sample.Main (file:/C:/sample/) to method sun.nio.ch.Util.getTemporaryDirectBuffer(int)
WARNING: Please consider reporting this to the maintainers of com.company.Main
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release

Ceci signifie qu’un module n’a pas exporté le package qui fait l’objet d’un accès par réflexion. Le package est encapsulé dans le module. Il s’agit fondamentalement d’une API interne. Quand votre objectif est d’avoir une application opérationnelle sur Java 11, vous pouvez commencer par ignorer cet avertissement. Le runtime Java 11 autorise l’accès réfléchissant pour que le code hérité puisse continuer à fonctionner.

Pour mettre fin à cet avertissement, recherchez le code mis à jour qui n’utilise pas l’API interne. Si vous ne parvenez pas à résoudre le problème avec le code mis à jour, vous pouvez utiliser l’option de ligne de commande --add-exports ou --add-opens pour ouvrir l’accès au package. Ces options permettent d’accéder aux types non exportés d’un module à partir d’un autre module.

L’option --add-exports permet au module cible d’accéder aux types publics du package nommé du module source. Parfois, le code utilise setAccessible(true) pour accéder aux membres non publics et à l’API. C’est ce que l’on appelle une réflexion profonde. Dans ce cas, utilisez --add-opens pour permettre à votre code d’accéder aux membres non publics d’un package. Si vous ne savez pas si vous devez utiliser --add-exports ou --add-opens, commencez par --add-exports.

Les options --add-exports et --add-opens doivent être considérées comme une solution de contournement et non une solution à long terme. L’utilisation de ces options casse l’encapsulation du système de module, qui est destiné à empêcher l’utilisation de l’API interne du JDK. Si l’API interne est supprimée ou modifiée, l’application échoue. L’accès réfléchissant est refusé dans Java 16, sauf si l’accès est activé à l’aide d’options de ligne de commande telles que --add-opens. Pour simuler le comportement futur, définissez --illegal-access=deny sur la ligne de commande.

L’avertissement dans l’exemple ci-dessus est émis car le package sun.nio.ch n’est pas exporté par le module java.base. En d’autres termes, il n’y a pas de exports sun.nio.ch; dans le fichier module-info.java du module java.base. Ceci peut être résolu avec --add-exports=java.base/sun.nio.ch=ALL-UNNAMED. Les classes qui ne sont pas définies dans un module appartiennent implicitement au module sans nom littéralement nommé ALL-UNNAMED.

java.lang.reflect.InaccessibleObjectException

Cette exception indique que vous essayez d’appeler setAccessible(true) sur un champ ou une méthode d’une classe encapsulée. Vous pouvez également recevoir un avertissement d’accès réfléchissant illégal. Utilisez l’option --add-opens pour permettre à votre code d’accéder aux membres non publics d’un package. Le message d’exception indiquera que le module n’ouvre pas le package au module qui tente d’appeler setAccessible. Si le module est un « module sans nom », utilisez UNNAMED-MODULE comme module cible dans l’option --add-opens.

java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.ArrayList jdk.internal.loader.URLClassPath.loaders accessible: 
module java.base does not "opens jdk.internal.loader" to unnamed module @6442b0a6

$ java --add-opens=java.base/jdk.internal.loader=UNNAMED-MODULE example.Main

java.lang.NoClassDefFoundError

L’erreur NoClassDefFoundError est généralement due à un split-package ou au référencement de modules supprimés.

Erreur NoClassDefFoundError due à des split-packages

Un split-package est un package présent dans plusieurs bibliothèques. Si une classe est introuvable alors que vous êtes certain du chemin de classe, cela peut indiquer un problème de split-package.

Ce problème se produit uniquement si le chemin de module est utilisé. Le système de module Java optimise la recherche de classe en limitant un package à un module nommé. Lors d’une recherche de classe, le runtime privilégie le chemin de module par rapport au chemin de classe. Si un package est partagé entre un module et le chemin de classe, seul le module est utilisé pour la recherche de classe. Ceci peut entraîner des erreurs NoClassDefFound.

Un moyen simple de vérifier la présence d’un split-package consiste à intégrer le chemin de module et le chemin de la classe dans jdeps et à utiliser le chemin de vos fichiers de classe d’application pour <path>. Si un split-package est présent, jdeps affiche l’avertissement Warning: split package: <package-name> <module-path> <split-path>.

Pour résoudre ce problème, vous pouvez utiliser --patch-module <module-name>=<path>[,<path>] pour ajouter le split-package au module nommé.

Erreur NoClassDefFoundError due à l’utilisation de modules Java EE ou CORBA

Si l’application s’exécute sur Java 8 mais déclenche une erreur java.lang.NoClassDefFoundError ou un java.lang.ClassNotFoundException, il est probable qu’elle utilise un package des modules Java EE ou CORBA. Ces modules ont été dépréciés dans Java 9 et supprimés dans Java 11.

Pour résoudre ce problème, ajoutez une dépendance d’exécution à votre projet.

Module supprimé Package affecté Dépendance suggérée
Java API for XML Web Services (JAX-WS) java.xml.ws JAX WS RI Runtime
Java Architecture for XML Binding (JAXB) java.xml.bind JAXB Runtime
JavaBeans Activation Framework (JAV) java.activation JavaBeans (TM) Activation Framework
Common Annotations java.xml.ws.annotation Javax Annotation API
Common Object Request Broker Architecture (CORBA) java.corba GlassFish CORBA ORB
Java Transaction API (JTA) java.transaction Java Transaction API

-Xbootclasspath/p is no longer a supported option (L’option -Xbootclasspath/p n’est plus prise en charge)

-Xbootclasspath/p n’est plus pris en charge. Utilisez --patch-module à la place. L’option --patch-module est décrite dans le document JEP 261. Recherchez la section intitulée « Patching module content » (Correction du contenu des modules). --patch-module peut être utilisé avec javac et Java pour remplacer les classes d’un module ou en ajouter.

En fait, --patch-module insère le module correctif dans la recherche de classe du système de module. Le système de module va d’abord extraire la classe du module correctif. Ceci revient à ajouter le chemin de classe de démarrage en préfixe dans Java 8.

UnsupportedClassVersionError

Cette exception signifie que vous essayez d’exécuter du code qui a été compilé avec une version ultérieure de Java sur une version antérieure de Java. Par exemple, vous exécutez du code sur Java 11 avec un fichier jar qui a été compilé avec le JDK 13.

Version de Java Version du format de fichier de classe
8 52
9 53
10 54
11 55
12 56
13 57

Étapes suivantes

Dès lors que l’application s’exécute sur Java 11, envisagez de déplacer les bibliothèques du chemin de classe vers le chemin de module. Recherchez les versions mises à jour des bibliothèques dont dépend votre application. Choisissez des bibliothèques modulaires si elles sont disponibles. Utilisez le chemin de module autant que possible, même si vous n’envisagez pas d’utiliser des modules dans votre application. Vous obtiendrez de meilleures performances pour le chargement des classes en utilisant le chemin de module plutôt que le chemin de classe.