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.
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:
Class.forName
avec la valeur par défaut ClassLoader
. Dans le cadre d'un certain ClassLoader
, toutes les classes ont des noms uniques.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.)toString
ou de la journalisation, mais il n’est pas garanti qu’il soit unique.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
:
void
, les quatre méthodes renvoient simplement son nom.Maintenant les règles pour la méthode getName()
:
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.$$Lambda$
, suivi d'un numéro, suivi d'une barre oblique, suivi d'un autre numéro.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).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()
:
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()
:
getCanonicalName()
renvoie exactement ce que renvoie la méthode getName()
.getCanonicalName()
renvoie null
pour les classes anonymes ou locales et pour les classes de tableaux de celles-ci.getCanonicalName()
renvoie ce que la méthode getName()
remplacera les signes dollar introduits par le compilateur par des points.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()
:
getSimpleName()
renvoie le nom de la classe tel qu'il est écrit dans le fichier source.getSimpleName()
renvoie un String
vide.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.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.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[]
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.
c'est le meilleur document que j'ai trouvé décrivant getName (), getSimpleName (), getCanonicalName ()
// 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[]
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.
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