web-dev-qa-db-fra.com

Quelle est la différence entre nom canonique, nom simple et nom de classe dans Java Class?

En Java, quelle est la différence entre ceux-ci:

Object o1 = ....
o1.getClass().getSimpleName();
o1.getClass().getName();
o1.getClass().getCanonicalName();

J'ai vérifié le Javadoc plusieurs fois et pourtant cela ne l'explique jamais bien. J'ai également effectué un test et cela ne reflétait aucune signification réelle derrière la manière dont ces méthodes s'appellent.

887

Si vous n'êtes pas sûr de quelque chose, essayez d'abord de passer un test.

J'ai fait ça:

class ClassNameTest {
    public static void main(final String... arguments) {
        printNamesForClass(
            int.class,
            "int.class (primitive)");
        printNamesForClass(
            String.class,
            "String.class (ordinary class)");
        printNamesForClass(
            Java.util.HashMap.SimpleEntry.class,
            "Java.util.HashMap.SimpleEntry.class (nested class)");
        printNamesForClass(
            new Java.io.Serializable(){}.getClass(),
            "new Java.io.Serializable(){}.getClass() (anonymous inner class)");
    }

    private static void printNamesForClass(final Class<?> clazz, final String label) {
        System.out.println(label + ":");
        System.out.println("    getName():          " + clazz.getName());
        System.out.println("    getCanonicalName(): " + clazz.getCanonicalName());
        System.out.println("    getSimpleName():    " + clazz.getSimpleName());
        System.out.println("    getTypeName():      " + clazz.getTypeName()); // added in Java 8
        System.out.println();
    }
}

Impressions:

 int.class (primitive): 
 getName (): int 
 getCanonicalName (): int 
 getSimpleName (): int 
 getTypeName () : int 
 
 String.class (classe ordinaire): 
 getName (): Java.lang.String 
 getCanonicalName (): Java.lang.String 
 getSimpleName (): String 
 getTypeName (): Java.lang.String 
 
 Java.util.HashMap.SimpleEntry.class (classe imbriquée): 
 getName (): Java.util.AbstractMap $ SimpleEntry 
 getCanonicalName (): Java.util.AbstractMap.SimpleEntry 
 getSimpleName (): SimpleEntry 
 getTypeName (): Java.util .AbstractMap $ SimpleEntry 
 
 New Java.io.Serializable () {}. GetClass () (classe interne anonyme): 
 GetName (): ClassNameTest $ 1 
. getCanonicalName (): null 
 getSimpleName (): 
 getTypeName (): ClassNameTest $ 1 

Il y a une entrée vide dans le dernier bloc où getSimpleName renvoie une chaîne vide.

Le résultat est:

  • le nom est le nom que vous utiliseriez pour charger dynamiquement la classe, par exemple, un appel à Class.forName avec la valeur par défaut ClassLoader. Dans le cadre d'un certain ClassLoader, toutes les classes ont des noms uniques.
  • le nom canonique est le nom qui serait utilisé dans une instruction d'importation. Cela peut être utile lors de toString ou des opérations de journalisation. Lorsque le compilateur javac a la vue complète d'un chemin de classe, il impose l'unicité des noms canoniques qu'il contient en mettant en conflit les noms de classe et de package pleinement qualifiés au moment de la compilation. Cependant, les machines virtuelles Java doivent accepter de tels conflits de noms. Par conséquent, les noms canoniques n'identifient pas les classes de manière unique dans un ClassLoader. (Avec le recul, un meilleur nom pour ce getter aurait été getJavaName; mais cette méthode date de l'époque où la machine virtuelle était utilisée uniquement pour exécuter des programmes Java.)
  • le nom simple identifie vaguement la classe; cela peut être utile à nouveau lors des opérations de toString ou de la journalisation, mais il n’est pas garanti qu’il soit unique.
  • le nom du type renvoie "une chaîne informative pour le nom de ce type", "c'est comme toString (): c'est purement informatif et n'a aucune valeur contractuelle" (comme écrit par sir4ur0n)
1046
Nick Holt

Ajout de classes locales, de lambdas et de la méthode toString() pour compléter les deux réponses précédentes. De plus, j'ajoute des tableaux de lambdas et des tableaux de classes anonymes (qui n'ont toutefois aucun sens dans la pratique):

package com.example;

public final class TestClassNames {
    private static void showClass(Class<?> c) {
        System.out.println("getName():          " + c.getName());
        System.out.println("getCanonicalName(): " + c.getCanonicalName());
        System.out.println("getSimpleName():    " + c.getSimpleName());
        System.out.println("toString():         " + c.toString());
        System.out.println();
    }

    private static void x(Runnable r) {
        showClass(r.getClass());
        showClass(Java.lang.reflect.Array.newInstance(r.getClass(), 1).getClass()); // Obtains an array class of a lambda base type.
    }

    public static class NestedClass {}

    public class InnerClass {}

    public static void main(String[] args) {
        class LocalClass {}
        showClass(void.class);
        showClass(int.class);
        showClass(String.class);
        showClass(Runnable.class);
        showClass(SomeEnum.class);
        showClass(SomeAnnotation.class);
        showClass(int[].class);
        showClass(String[].class);
        showClass(NestedClass.class);
        showClass(InnerClass.class);
        showClass(LocalClass.class);
        showClass(LocalClass[].class);
        Object anonymous = new Java.io.Serializable() {};
        showClass(anonymous.getClass());
        showClass(Java.lang.reflect.Array.newInstance(anonymous.getClass(), 1).getClass()); // Obtains an array class of an anonymous base type.
        x(() -> {});
    }
}

enum SomeEnum {
   BLUE, YELLOW, RED;
}

@interface SomeAnnotation {}

C'est la sortie complète:

getName():          void
getCanonicalName(): void
getSimpleName():    void
toString():         void

getName():          int
getCanonicalName(): int
getSimpleName():    int
toString():         int

getName():          Java.lang.String
getCanonicalName(): Java.lang.String
getSimpleName():    String
toString():         class Java.lang.String

getName():          Java.lang.Runnable
getCanonicalName(): Java.lang.Runnable
getSimpleName():    Runnable
toString():         interface Java.lang.Runnable

getName():          com.example.SomeEnum
getCanonicalName(): com.example.SomeEnum
getSimpleName():    SomeEnum
toString():         class com.example.SomeEnum

getName():          com.example.SomeAnnotation
getCanonicalName(): com.example.SomeAnnotation
getSimpleName():    SomeAnnotation
toString():         interface com.example.SomeAnnotation

getName():          [I
getCanonicalName(): int[]
getSimpleName():    int[]
toString():         class [I

getName():          [Ljava.lang.String;
getCanonicalName(): Java.lang.String[]
getSimpleName():    String[]
toString():         class [Ljava.lang.String;

getName():          com.example.TestClassNames$NestedClass
getCanonicalName(): com.example.TestClassNames.NestedClass
getSimpleName():    NestedClass
toString():         class com.example.TestClassNames$NestedClass

getName():          com.example.TestClassNames$InnerClass
getCanonicalName(): com.example.TestClassNames.InnerClass
getSimpleName():    InnerClass
toString():         class com.example.TestClassNames$InnerClass

getName():          com.example.TestClassNames$1LocalClass
getCanonicalName(): null
getSimpleName():    LocalClass
toString():         class com.example.TestClassNames$1LocalClass

getName():          [Lcom.example.TestClassNames$1LocalClass;
getCanonicalName(): null
getSimpleName():    LocalClass[]
toString():         class [Lcom.example.TestClassNames$1LocalClass;

getName():          com.example.TestClassNames$1
getCanonicalName(): null
getSimpleName():    
toString():         class com.example.TestClassNames$1

getName():          [Lcom.example.TestClassNames$1;
getCanonicalName(): null
getSimpleName():    []
toString():         class [Lcom.example.TestClassNames$1;

getName():          com.example.TestClassNames$$Lambda$1/1175962212
getCanonicalName(): com.example.TestClassNames$$Lambda$1/1175962212
getSimpleName():    TestClassNames$$Lambda$1/1175962212
toString():         class com.example.TestClassNames$$Lambda$1/1175962212

getName():          [Lcom.example.TestClassNames$$Lambda$1;
getCanonicalName(): com.example.TestClassNames$$Lambda$1/1175962212[]
getSimpleName():    TestClassNames$$Lambda$1/1175962212[]
toString():         class [Lcom.example.TestClassNames$$Lambda$1;

Alors, voici les règles. Commençons par les types primitifs et void:

  1. Si l'objet de classe représente un type primitif ou void, les quatre méthodes renvoient simplement son nom.

Maintenant les règles pour la méthode getName():

  1. Chaque classe ou interface non lambda et non tableau (c'est-à-dire, niveau supérieur, imbriqué, interne, local et anonyme) a un nom (qui est renvoyé par getName()) qui correspond au nom du package suivi d'un point ( s'il existe un paquet), suivi du nom de son fichier de classe tel qu'il est généré par le compilateur (sans le suffixe .class). S'il n'y a pas de paquet, c'est simplement le nom du fichier de classe. Si la classe est une classe interne, imbriquée, locale ou anonyme, le compilateur doit générer au moins un $ dans son nom de fichier de classe. Notez que pour les classes anonymes, le nom de la classe se terminerait par un signe dollar suivi d'un nombre.
  2. Les noms de classe Lambda sont généralement imprévisibles et vous ne devriez pas vous en soucier de toute façon. Exactement, leur nom est le nom de la classe englobante, suivi de $$Lambda$, suivi d'un numéro, suivi d'une barre oblique, suivi d'un autre numéro.
  3. Les descripteurs de classe des primitives sont Z pour boolean, B pour byte, S pour short, C pour char, I pour int, J pour long, F pour float et D pour double. Pour les classes et les interfaces autres que les tableaux, le descripteur de classe est L suivi de ce qui est donné par getName() suivi de ;. Pour les classes de tableau, le descripteur de classe est [, suivi du descripteur de classe du type de composant (qui peut être lui-même une autre classe de tableau).
  4. Pour les classes de tableaux, la méthode getName() renvoie son descripteur de classe. Cette règle ne semble échouer que pour les classes de tableau dont le type de composant est un lambda (ce qui est peut-être un bogue), mais espérons que cela ne devrait pas avoir d'importance, car il n'y a aucun intérêt, même sur l'existence de classes de tableau dont le type de composant est un lambda.

Maintenant, la méthode toString():

  1. Si l'instance de classe représente une interface (ou une annotation, qui est un type d'interface spécial), la toString() renvoie "interface " + getName(). S'il s'agit d'une primitive, elle retourne simplement getName(). S'il s'agit de quelque chose d'autre (un type de classe, même s'il est assez étrange), il retourne "class " + getName().

La méthode getCanonicalName():

  1. Pour les classes et les interfaces de niveau supérieur, la méthode getCanonicalName() renvoie exactement ce que renvoie la méthode getName().
  2. La méthode getCanonicalName() renvoie null pour les classes anonymes ou locales et pour les classes de tableaux de celles-ci.
  3. Pour les classes et interfaces internes et imbriquées, la méthode getCanonicalName() renvoie ce que la méthode getName() remplacera les signes dollar introduits par le compilateur par des points.
  4. Pour les classes de tableaux, la méthode getCanonicalName() renvoie null si le nom canonique du type de composant est null. Sinon, il renvoie le nom canonique du type de composant suivi de [].

La méthode getSimpleName():

  1. Pour les classes de niveau supérieur, imbriquées, internes et locales, la getSimpleName() renvoie le nom de la classe tel qu'il est écrit dans le fichier source.
  2. Pour les classes anonymes, la getSimpleName() renvoie un String vide.
  3. Pour les classes lambda, la getSimpleName() ne renvoie que ce que la getName() renverrait sans le nom du paquet. Cela n'a pas beaucoup de sens et ressemble à un bogue pour moi, mais il est inutile d'appeler getSimpleName() sur une classe lambda pour commencer.
  4. Pour les classes de tableaux, la méthode getSimpleName() renvoie le nom simple de la classe de composant suivi de []. Cela a l’effet secondaire amusant/étrange que les classes de tableaux dont le type de composant est une classe anonyme aient simplement [] comme noms simples.
79
Victor Stafusa

En plus des observations de Nick Holt, j'ai exécuté quelques observations pour le type de données Array:

//primitive Array
int demo[] = new int[5];
Class<? extends int[]> clzz = demo.getClass();
System.out.println(clzz.getName());
System.out.println(clzz.getCanonicalName());
System.out.println(clzz.getSimpleName());       

System.out.println();


//Object Array
Integer demo[] = new Integer[5]; 
Class<? extends Integer[]> clzz = demo.getClass();
System.out.println(clzz.getName());
System.out.println(clzz.getCanonicalName());
System.out.println(clzz.getSimpleName());

Extraits de code ci-dessus:

[I
int[]
int[]

[Ljava.lang.Integer;
Java.lang.Integer[]
Integer[]
77
gerardw

J'ai également été troublé par le large éventail de systèmes de nommage, et j'étais sur le point de poser et de répondre à ma propre question à ce sujet lorsque j'ai trouvé cette question ici. Je pense que mes conclusions vont assez bien et complètent ce qui est déjà ici. Mon objectif est de rechercher de la documentation sur les différents termes et d’ajouter des termes plus connexes susceptibles de se retrouver à d’autres endroits.

Prenons l'exemple suivant:

package a.b;
class C {
  static class D extends C {
  }
  D d;
  D[] ds;
}
  • Le nom simple de D est D. C'est juste la partie que vous avez écrite lors de la déclaration de la classe. Les classes anonymes n'ont pas de nom simple. Class.getSimpleName() renvoie ce nom ou la chaîne vide. Il est possible que le nom simple contienne un $ si vous l'écrivez comme ceci, car $ est une partie valide d'un identifiant selon section 3.8 de JLS (même si c'est un peu découragé).

  • Selon la section JLS 6.7 , a.b.C.D et a.b.C.D.D.D seraient tous deux des noms complets , mais seul a.b.C.D serait le nom canonique de D. Donc, chaque nom canonique est un nom complet, mais le nom converge n'est pas toujours vrai. Class.getCanonicalName() retournera le nom canonique ou null.

  • Class.getName() est documenté pour renvoyer le nom binaire , comme spécifié dans section 13.1 de JLS . Dans ce cas, il retourne a.b.C$D pour D et [La.b.C$D; pour D[].

  • Cette réponse montre qu'il est possible que deux classes chargées par le même chargeur de classes aient le même nom canonique mais distinct noms binaires . Aucun nom n'est suffisant pour déduire l'autre de manière fiable: si vous avez le nom canonique, vous ne savez pas quelles parties du nom sont des packages et lesquelles contiennent des classes. Si vous avez le nom binaire, vous ne savez pas quels $ ont été introduits en tant que séparateurs et lesquels faisaient partie d'un nom simple. (Le fichier de classe stocke le nom binaire de la classe elle-même et ses classe englobante , ce qui permet à l'exécution de faites cette distinction .)

  • classes anonymes et classes locales n'ont aucun nom complet mais ont toujours un nom binaire . Il en va de même pour les classes imbriquées dans ces classes. Chaque classe a un nom binaire.

  • Lancer javap -v -private sur a/b/C.class indique que le pseudo-code fait référence au type de d comme La/b/C$D; et à celui du tableau ds comme [La/b/C$D;. Ceux-ci sont appelés descripteurs , et ils sont spécifiés dans section 4.3 de JVMS .

  • Le nom de classe a/b/C$D utilisé dans ces deux descripteurs correspond à ce que vous obtenez en remplaçant . par / dans le nom binaire. La spécification JVM appelle apparemment ceci la forme interne du nom binaire . section 4.2.1 de JVMS le décrit et indique que la différence par rapport au nom binaire était due à des raisons historiques.

  • Le nom de fichier d'une des chargeurs de classes typiques basés sur un nom de fichier est ce que vous obtenez si vous interprétez le / dans le fichier interne. forme du nom binaire en tant que séparateur de répertoire et ajoutez-y l'extension de nom de fichier .class. Il est résolu par rapport au chemin de classe utilisé par le chargeur de classes en question.

12
MvG

c'est le meilleur document que j'ai trouvé décrivant getName (), getSimpleName (), getCanonicalName ()

https://javahowtodoit.wordpress.com/2014/09/09/Java-lang-class-what-is-the-difference-between-class-class-getname-class-getcanonicalname-and-class-getsimplename/

// Primitive type
int.class.getName();          // -> int
int.class.getCanonicalName(); // -> int
int.class.getSimpleName();    // -> int

// Standard class
Integer.class.getName();          // -> Java.lang.Integer
Integer.class.getCanonicalName(); // -> Java.lang.Integer
Integer.class.getSimpleName();    // -> Integer

// Inner class
Map.Entry.class.getName();          // -> Java.util.Map$Entry
Map.Entry.class.getCanonicalName(); // -> Java.util.Map.Entry
Map.Entry.class.getSimpleName();    // -> Entry     

// Anonymous inner class
Class<?> anonymousInnerClass = new Cloneable() {}.getClass();
anonymousInnerClass.getName();          // -> somepackage.SomeClass$1
anonymousInnerClass.getCanonicalName(); // -> null
anonymousInnerClass.getSimpleName();    // -> // An empty string

// Array of primitives
Class<?> primitiveArrayClass = new int[0].getClass();
primitiveArrayClass.getName();          // -> [I
primitiveArrayClass.getCanonicalName(); // -> int[]
primitiveArrayClass.getSimpleName();    // -> int[]

// Array of objects
Class<?> objectArrayClass = new Integer[0].getClass();
objectArrayClass.getName();          // -> [Ljava.lang.Integer;
objectArrayClass.getCanonicalName(); // -> Java.lang.Integer[]
objectArrayClass.getSimpleName();    // -> Integer[]
10
Kiran

Il est intéressant de noter que getCanonicalName() et getSimpleName() peuvent augmenter InternalError lorsque le nom de la classe est mal formé. Cela se produit pour certains langages JVM non-Java, par exemple Scala.

Considérez ce qui suit (Scala 2.11 sur Java 8):

scala> case class C()
defined class C

scala> val c = C()
c: C = C()

scala> c.getClass.getSimpleName
Java.lang.InternalError: Malformed class name
  at Java.lang.Class.getSimpleName(Class.Java:1330)
  ... 32 elided

scala> c.getClass.getCanonicalName
Java.lang.InternalError: Malformed class name
  at Java.lang.Class.getSimpleName(Class.Java:1330)
  at Java.lang.Class.getCanonicalName(Class.Java:1399)
  ... 32 elided

scala> c.getClass.getName
res2: String = C

Cela peut poser un problème pour les environnements linguistiques mixtes ou les environnements qui chargent de manière dynamique le bytecode, par exemple les serveurs d'applications et autres logiciels de plate-forme.

3
Sim
    public void printReflectionClassNames(){
    StringBuffer buffer = new StringBuffer();
    Class clazz= buffer.getClass();
    System.out.println("Reflection on String Buffer Class");
    System.out.println("Name: "+clazz.getName());
    System.out.println("Simple Name: "+clazz.getSimpleName());
    System.out.println("Canonical Name: "+clazz.getCanonicalName());
    System.out.println("Type Name: "+clazz.getTypeName());
}

outputs:
Reflection on String Buffer Class
Name: Java.lang.StringBuffer
Simple Name: StringBuffer
Canonical Name: Java.lang.StringBuffer
Type Name: Java.lang.StringBuffer
1
Shirish Singh