W3docs

Objets Class en Java

Obtenez des objets Class<T> en Java avec Object.getClass(), les littéraux .class et Class.forName.

Tout en reflection commence par un objet Class. Pour chaque type chargé par la JVM — chaque classe, interface, type tableau, enum, annotation, et même chaque primitif — il existe exactement une instance Class qui le décrit. Cet objet est votre point d'accès à la structure du type : son nom, sa superclasse, ses membres, ses annotations. Ce chapitre couvre les trois façons d'obtenir un Class, ce que Class<T> contient, et les petites surprises qui piègent les développeurs.

Trois façons d'obtenir un Class

Il existe exactement trois routes, et chacune convient à une situation différente.

1. Le littéral .class — vous connaissez le type à la compilation.

Class<String> c1 = String.class;
Class<int[]> c2 = int[].class;
Class<Integer> c3 = int.class == Integer.class ? null : int.class;   // see "primitives" below

C'est le plus sûr à la compilation et le plus rapide — il n'y a pas de recherche, le compilateur intègre directement une référence. Utilisez-le chaque fois que vous pouvez nommer le type.

2. Object.getClass() — vous avez une instance.

Object o = "hello";
Class<?> c = o.getClass();        // class java.lang.String

getClass() renvoie la classe à l'exécution de l'objet, qui peut être une sous-classe du type de variable déclaré. Object o = new ArrayList<>() donne o.getClass() == ArrayList.class, et non Object.class. Son type statique est Class<?> car le compilateur sait seulement que o est un certain Object.

3. Class.forName(String) — vous n'avez qu'un nom.

Class<?> c = Class.forName("java.util.ArrayList");

C'est la route dynamique : un nom de classe entièrement qualifié sous forme de chaîne, résolu à l'exécution. Elle lève ClassNotFoundException si aucune classe de ce nom ne peut être chargée. C'est ce qu'utilisent les chargeurs de plugins et les pilotes JDBC. Il existe une variante chargée mais non initialisée : Class.forName(name, false, classLoader) qui ignore les initialiseurs statiques jusqu'à la première utilisation active de la classe.

Ce que Class<T> contient

Un objet Class est une description riche. Les méthodes principales :

Class<?> c = ArrayList.class;

c.getName();              // "java.util.ArrayList"        (binary name)
c.getSimpleName();        // "ArrayList"
c.getCanonicalName();     // "java.util.ArrayList"        (source-like)
c.getPackageName();       // "java.util"
c.getSuperclass();        // class java.util.AbstractList
c.getInterfaces();        // [List, RandomAccess, Cloneable, Serializable]
c.getModifiers();         // int bitset → Modifier.isPublic(...) etc.
c.isInterface();          // false
c.isEnum(); c.isArray(); c.isPrimitive(); c.isAnnotation();

À partir d'un Class vous accédez à tous les membres : getDeclaredFields(), getDeclaredMethods(), getDeclaredConstructors(), ainsi que leurs équivalents publics/hérités get… (la distinction présentée dans le chapitre d'introduction). Les chapitres suivants entrent dans les détails de chacun.

Nom binaire, nom simple et nom canonique

Les méthodes de nommage diffèrent de façon surprenante lorsque vous les journalisez ou les comparez :

TypegetName()getSimpleName()getCanonicalName()
Stringjava.lang.StringStringjava.lang.String
int[][Iint[]int[]
String[][Ljava.lang.String;String[]java.lang.String[]
Map.Entry imbriquéjava.util.Map$EntryEntryjava.util.Map.Entry
classe anonymeOuter$1"" (vide)null

getName() est le nom binaire — la forme interne de la JVM, avec $ pour l'imbrication et l'encodage cryptique [I / [L…; pour les tableaux. C'est ce qu'attend Class.forName. getCanonicalName() est la forme source que vous écririez, et elle vaut null pour les types que vous ne pouvez pas nommer dans le code source (locaux, classes anonymes). Utilisez getName() pour les allers-retours avec forName ; utilisez getSimpleName()/getCanonicalName() pour la sortie lisible.

Les primitifs et les tableaux ont aussi des objets Class

Chaque primitif a son propre Class, distinct de son wrapper :

int.class == Integer.class      // false — two different Class objects
int.class.getName()             // "int"
Integer.TYPE == int.class       // true — TYPE is the primitive Class

void dispose même de void.class (et de Void.TYPE). Les classes tableau sont synthétisées par la JVM : int[].class, String[][].class. arrayClass.getComponentType() enlève une dimension (String[].class.getComponentType() vaut String.class). Ces distinctions importent quand vous faites correspondre les types de paramètres dans getMethodgetMethod("foo", int.class) et getMethod("foo", Integer.class) trouvent des surcharges différentes.

Identité de classe et chargeurs de classes

L'identité d'un objet Class ne tient pas uniquement à son nom — c'est la paire (nom, chargeur de classe définissant). Le même fichier .class chargé par deux chargeurs de classes différents produit deux objets Class distincts et incompatibles. Un cast entre eux lève ClassCastException même si les noms correspondent. Cela est généralement invisible dans une application simple (un seul chargeur) mais est à l'origine de nombreuses énigmes "c'est pourtant la même classe !" dans les serveurs d'applications, OSGi et les systèmes de rechargement à chaud. Pour la reflection ordinaire, traitez les objets Class comme des singletons par type et comparez-les avec ==.

Exemple concret : sonder les types de trois façons

Le programme obtient des objets Class par les trois routes, puis interroge quelques types — une classe normale, une interface, un tableau, un primitif et un type imbriqué — pour faire apparaître les différences de nommage et de structure.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • Les trois routes ont convergé vers le même type d'objet : un littéral .class, un appel à getClass() et une recherche forName ont chacun produit un Class pleinement utilisable. La route choisie dépend de ce que vous connaissez (le type, une instance, ou seulement un nom) — le résultat est identique en termes de capacités.
  • getClass() sur la variable Greeter g a retourné Robot, pas Greeter. Le type déclaré est sans importance ; getClass() rapporte toujours la classe concrète à l'exécution. C'est pourquoi la distribution polymorphe et l'inspection par reflection voient le même type "réel".
  • Les trois méthodes de nommage ont divergé exactement là où le tableau le prédit : String[] a affiché le nom binaire [Ljava.lang.String; avec getName(), mais la forme lisible String[] avec les formes simple et canonique. Si vous réinjectez un nom dans forName, il doit être sous la forme de getName().
  • int.class == Integer.class valait false, tandis que Integer.TYPE == int.class valait true. Le primitif et son wrapper sont des objets Class distincts, et Integer.TYPE n'est qu'un alias pour le primitif. Les confondre est la cause classique de NoSuchMethodException lors de la recherche d'une surcharge par type de paramètre.
  • Robot.class == new Robot().getClass() valait true : au sein d'un seul chargeur de classes, un type correspond à exactement un objet Class, donc == est la comparaison correcte. Vous n'avez jamais besoin de .equals() sur des objets Class dans du code à chargeur unique.

Pièges courants

  • forName exécute les initialiseurs statiques (dans sa forme à un argument). Charger une classe peut avoir des effets de bord. Utilisez la forme à trois arguments avec initialize=false si vous souhaitez seulement inspecter.
  • getSimpleName() peut être vide (classes anonymes) et getCanonicalName() peut être null (locaux, anonymes). Ne supposez pas qu'ils sont toujours des identifiants imprimables.
  • Les génériques sont effacés. List<String>.class est illégal ; il n'y a que List.class. Un Class ne porte aucune information sur les arguments de type — cela réside dans Type/ParameterizedType, une API de reflection séparée (et plus avancée). Voir restrictions des génériques pour comprendre pourquoi l'effacement fonctionne ainsi.

L'objet Class en main, le chapitre suivant ouvre le premier tiroir des membres : les champs — les inspecter, les lire et les écrire, même lorsqu'ils sont privés ou finaux.

Pratique

Pratique
Vous avez 'Object o = new java.util.LinkedList<String>();' déclaré comme 'Object'. Vous appelez 'o.getClass().getName()'. Quelle chaîne est retournée, et pourquoi n'est-ce pas 'java.lang.Object' ?
Vous avez 'Object o = new java.util.LinkedList<String>();' déclaré comme 'Object'. Vous appelez 'o.getClass().getName()'. Quelle chaîne est retournée, et pourquoi n'est-ce pas 'java.lang.Object' ?
Was this page helpful?