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


974

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 façon dont ces méthodes sont appelées.



218
Je pense que c'est une question raisonnable. Le javadoc ne fait pas du bon travail pour expliquer la différence entre les trois.
Graham Borland

1
Voir - docs.oracle.com/javase/6/docs/api/java/lang/Class.html ou peut-être simplement écrire un test.
Nick Holt

7
@GrahamBorland Le javadoc dit "tel que défini par la spécification du langage Java" - vous pouvez donc le rechercher dans ce document. Tout simplement parce que ce n'est pas un lien cliquable, les gens peuvent toujours faire un effort minimal et cliquer sur le premier résultat du moteur de recherche.
vbence

66
@vbence: La plupart des gens préfèrent faire avancer les choses plutôt que de rechercher le JLS pour des choses triviales comme celle-ci. C'est donc le premier résultat Google :)
pathikrit

Réponses:


1130

Si vous n'êtes pas sûr de quelque chose, essayez d'abord d'écrire 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();
    }
}

Tirages:

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ù getSimpleNamerenvoie une chaîne vide.

Le résultat en regardant ceci est:

  • le nom est le nom que vous utiliseriez pour charger dynamiquement la classe avec, par exemple, un appel à Class.forNameavec 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. Il peut être utile pendant toStringou lors des opérations de journalisation. Lorsque le javaccompilateur a une vue complète d'un chemin de classe, il applique l'unicité des noms canoniques en le confrontant aux 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, et donc les noms canoniques n'identifient pas de manière unique les classes dans a ClassLoader. (Rétrospectivement, un meilleur nom pour cet getter aurait été getJavaName; mais cette méthode date d'une époque où la JVM n'était utilisée que pour exécuter des programmes Java.)
  • le nom simple identifie de manière lâche la classe, peut être utile à nouveau pendant toStringles opérations de journalisation, mais n'est pas garanti d'être 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 pas de valeur contractuelle" (comme écrit par sir4ur0n)

5
Quel supplément pensez-vous nécessaire?
Nick Holt

2
@AnupamSaini oui. Avoir un tel nom de package dans une application réelle serait fou.
Jayen

3
L'informatique serait folle, cependant, c'est le genre d'hypothèse qui permettrait à un acteur malveillant de travailler. Quelqu'un qui dit "oh, eh bien nous savons que les classes ne commenceront jamais par des minuscules / les packages ne commenceront jamais par des majuscules". Certes, un acteur malveillant qui a accès à votre chargeur de classe peut déjà faire des choses terribles, donc ce n'est probablement pas une hypothèse absolument terrible.
corsiKa

2
@PieterDeBie Comment ça? Tout ce que vous devez savoir, c'est le nom de la méthode que vous souhaitez tester.
fool4jesus

20
Java 8 a également ajouté getTypeName () ... vous voulez mettre à jour pour cela?
Theodore Murdock

90

Ajout de classes locales, de lambdas et de la toString()méthode 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 cependant 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 {}

Voici 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;

Voici donc les règles. Tout d'abord, commençons par les types primitifs et void:

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

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

  1. Chaque classe ou interface non lambda et non tableau (c'est-à-dire de niveau supérieur, imbriqué, interne, local et anonyme) a un nom (qui est renvoyé par getName()) qui est le nom du package suivi d'un point (s'il y a un package ), suivi du nom de son fichier de classe tel que généré par le compilateur (sans le suffixe .class). S'il n'y a pas de package, c'est simplement le nom du fichier de classe. Si la classe est une classe interne, imbriquée, locale ou anonyme, le compilateur doit en générer au moins une $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 nombre, suivi d'une barre oblique, suivi d'un autre nombre.
  3. Le descripteur de classe des primitives est Zpour boolean, Bpour byte, Spour short, Cpour char, Ipour int, Jpour long, Fpour floatet Dpour double. Pour les classes et interfaces non matricielles, le descripteur de classe est Lsuivi 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 getName()méthode renvoie son descripteur de classe. Cette règle semble échouer uniquement pour les classes de tableau dont le type de composant est un lambda (ce qui est peut-être un bogue), mais j'espère que cela ne devrait pas avoir d'importance de toute façon car il n'y a aucun point même sur l'existence de classes de tableau dont le type de composant est un lambda.

Maintenant, la toString()méthode:

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

La getCanonicalName()méthode:

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

La getSimpleName()méthode:

  1. Pour les classes de niveau supérieur, imbriquées, internes et locales, le 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 vide String.
  3. Pour les classes lambda, le getSimpleName()renvoie simplement ce que le getName()retournerait sans le nom du package. Cela n'a pas beaucoup de sens et ressemble à un bogue pour moi, mais il est inutile d'appeler getSimpleName()une classe lambda pour commencer.
  4. Pour les classes de tableaux, la getSimpleName()méthode renvoie le nom simple de la classe de composants suivi de []. Cela a pour effet secondaire drôle / bizarre que les classes de tableaux dont le type de composant est une classe anonyme ont tout []comme leurs noms simples.

2
… replacing the dollar-signs by dots: Seuls les signes dollar introduits comme délimiteurs sont remplacés. Vous pouvez très bien avoir des dollars dans un nom simple, et ceux-ci resteront en place.
MvG

Oh non! Dans le cadre du nom de classe! Je suis en train de développer un transformateur de classe et je pensais que '/' serait un délimiteur sûr entre la classe et le nom du package: /
José Roberto Araújo Júnior

81

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

//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());

Copies d'extrait de code ci-dessus:

[I
int[]
int[]

[Ljava.lang.Integer;
java.lang.Integer[]
Integer[]

28
Il ne serait pas préférable de proposer une modification de la réponse ci-dessus.
LoKi

17

J'ai également été confondu par le large éventail de schémas de nommage différents, 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 résultats correspondent assez bien et complètent ce qui est déjà là. Je me concentre sur la recherche de documentation sur les différents termes et sur l'ajout d'autres termes connexes qui pourraient apparaître à 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 Dest 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 il $s'agit d'une partie valide d'un identifiant selon la section 3.8 de JLS (même s'il est quelque peu déconseillé).

  • Selon la section 6.7 de JLS , les deux a.b.C.Det a.b.C.D.D.Dseraient des noms pleinement qualifiés , mais seul a.b.C.Dserait le nom canonique de D. Ainsi, chaque nom canonique est un nom complet, mais l'inverse n'est pas toujours vrai. Class.getCanonicalName()renverra le nom canonique ou null.

  • Class.getName()est documenté pour renvoyer le nom binaire , comme spécifié dans la section 13.1 de JLS . Dans ce cas, il revient a.b.C$Dpour Det [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 classe aient le même nom canonique mais des noms binaires distincts . Aucun nom n'est suffisant pour déduire de manière fiable l'autre: 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 lesquels $ont été introduits comme séparateurs et qui faisaient partie d'un nom simple. (Le fichier de classe stocke le nom binaire de la classe elle - même et de sa classe englobante , ce qui permet au runtime de faire cette distinction .)

  • Les classes anonymes et les classes locales n'ont pas de 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.

  • Fonctionnement javap -v -private sur a/b/C.classmontre que le bytecode fait référence au type de das La/b/C$D;et à celui du tableau dsas [La/b/C$D;. Ils sont appelés descripteurs et ils sont spécifiés dans la section 4.3 de JVMS .

  • Le nom de classe a/b/C$Dutilisé dans ces deux descripteurs est ce que vous obtenez en remplaçant .par /dans le nom binaire. La spécification JVM appelle apparemment cela la forme interne du nom binaire . La section 4.2.1 de JVMS le décrit et déclare que la différence avec le nom binaire était pour des raisons historiques.

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


3
Cela devrait être la réponse acceptée car c'est la seule réponse qui fait référence au JLS et utilise les terminologies appropriées.
John

10

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-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[]

3

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

Tenez compte des éléments suivants (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 être un problème pour les environnements de langage mixte ou les environnements qui chargent dynamiquement le bytecode, par exemple, les serveurs d'applications et d'autres logiciels de plate-forme.


2

getName () - renvoie le nom de l'entité (classe, interface, classe de tableau, type primitif ou void) représentée par cet objet Class, sous la forme d'une chaîne.

getCanonicalName () - renvoie le nom canonique de la classe sous-jacente tel que défini par la spécification du langage Java.

getSimpleName () - renvoie le nom simple de la classe sous-jacente, c'est-à-dire le nom qui lui a été donné dans le code source.

package com.practice;

public class ClassName {
public static void main(String[] args) {

  ClassName c = new ClassName();
  Class cls = c.getClass();

  // returns the canonical name of the underlying class if it exists
  System.out.println("Class = " + cls.getCanonicalName());    //Class = com.practice.ClassName
  System.out.println("Class = " + cls.getName());             //Class = com.practice.ClassName
  System.out.println("Class = " + cls.getSimpleName());       //Class = ClassName
  System.out.println("Class = " + Map.Entry.class.getName());             // -> Class = java.util.Map$Entry
  System.out.println("Class = " + Map.Entry.class.getCanonicalName());    // -> Class = java.util.Map.Entry
  System.out.println("Class = " + Map.Entry.class.getSimpleName());       // -> Class = Entry 
  }
}

Une différence est que si vous utilisez une classe anonyme, vous pouvez obtenir une valeur nulle lorsque vous essayez d'obtenir le nom de la classe à l'aide de la classegetCanonicalName()

Un autre fait est que la getName()méthode se comporte différemment de la getCanonicalName()méthode des classes internes . getName()utilise un dollar comme séparateur entre le nom canonique de la classe englobante et le nom simple de la classe interne.

Pour en savoir plus sur la récupération d'un nom de classe en Java .


1
    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
Les deux premières lignes à l'intérieur de la méthode peuvent être réduites àClass<StringBuffer> clazz = StringBuffer.class
ThePyroEagle
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.