tipos - ¿Cuál es la diferencia entre nombre canónico, nombre simple y nombre de clase en Java Class?
que es una clase en java (7)
En Java, ¿cuál es la diferencia entre estos:
Object o1 = ....
o1.getClass().getSimpleName();
o1.getClass().getName();
o1.getClass().getCanonicalName();
He revisado el Javadoc varias veces y, sin embargo, esto nunca lo explica bien. También realicé una prueba y eso no reflejaba ningún significado real detrás de la forma en que se llaman estos métodos.
Además de las observaciones de Nick Holt, corrí algunos casos para el tipo de datos 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());
Sobre el fragmento de código se imprime:
[I
int[]
int[]
[Ljava.lang.Integer;
java.lang.Integer[]
Integer[]
Agregando clases locales, lambdas y el método toString()
para completar las dos respuestas anteriores. Además, agrego arrays de lambdas y arrays de clases anónimas (que no tienen sentido en la práctica, sin embargo):
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 {}
Esta es la salida completa:
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;
Entonces, aquí están las reglas. Primero, comencemos con tipos primitivos y void
:
- Si el objeto de clase representa un tipo primitivo o
void
, los cuatro métodos simplemente devuelven su nombre.
Ahora las reglas para el método getName()
:
- Cada clase o interfaz no lambda y no de matriz (es decir, de nivel superior, anidada, interna, local y anónima) tiene un nombre (que es devuelto por
getName()
) que es el nombre del paquete seguido de un punto (si existe) es un paquete), seguido del nombre de su archivo de clase como lo genera el compilador (sin el sufijo.class
). Si no hay ningún paquete, es simplemente el nombre del archivo de clase. Si la clase es interna, anidada, local o anónima, el compilador debe generar al menos un$
en su nombre de archivo de clase. Tenga en cuenta que para las clases anónimas, el nombre de la clase terminaría con un signo de dólar seguido de un número. - Los nombres de las clases Lambda son generalmente impredecibles, y no debería preocuparse por ellos de todos modos. Exactamente, su nombre es el nombre de la clase adjunta, seguido de
$$Lambda$
, seguido de un número, seguido de una barra oblicua, seguido de otro número. - El descriptor de clase de las primitivas es
Z
paraboolean
,B
parabyte
,S
parashort
,C
parachar
,I
paraint
,J
paralong
,F
parafloat
yD
paradouble
. Para las clases e interfaces que no son de matriz, el descriptor de la clase esL
seguido de lo que dagetName()
seguido de;
. Para las clases de matriz, el descriptor de clase es[
seguido del descriptor de clase del tipo de componente (que puede ser en sí mismo otra clase de matriz). - Para las clases de matriz, el método
getName()
devuelve su descriptor de clase. Esta regla parece fallar solo para las clases de matriz cuyo tipo de componente es lambda (que posiblemente sea un error), pero es de esperar que esto no importe de todos modos porque no tiene sentido ni siquiera la existencia de clases de matriz cuyo tipo de componente sea una lambda.
Ahora, el método toString()
:
- Si la instancia de la clase representa una interfaz (o una anotación, que es un tipo especial de interfaz),
toString()
devuelve"interface " + getName()
. Si es una primitiva, devuelve simplementegetName()
. Si es otra cosa (un tipo de clase, incluso si es bastante extraño), devuelve"class " + getName()
.
El método getCanonicalName()
:
- Para las clases e interfaces de nivel superior, el método
getCanonicalName()
devuelve lo que devuelve el métodogetName()
. - El método
getCanonicalName()
devuelvenull
para las clases anónimas o locales y para las clases de matriz de esas. - Para las clases e interfaces internas y anidadas, el método
getCanonicalName()
devuelve lo que el métodogetName()
reemplazaría los signos de dólar introducidos por el compilador por puntos. - Para las clases de matriz, el método
getCanonicalName()
devuelvenull
si el nombre canónico del tipo de componente esnull
. De lo contrario, devuelve el nombre canónico del tipo de componente seguido de[]
.
El método getSimpleName()
:
- Para las clases de nivel superior, anidadas, internas y locales, el
getSimpleName()
devuelve el nombre de la clase como está escrito en el archivo fuente. - Para las clases anónimas, el
getSimpleName()
devuelve unaString
vacía. - Para las clases lambda, el
getSimpleName()
simplemente devuelve lo que elgetName()
devolvería sin el nombre del paquete. Esto no tiene mucho sentido y parece un error para mí, pero no tiene sentido llamar agetSimpleName()
en una clase lambda para comenzar. - Para las clases de matriz, el método
getSimpleName()
devuelve el nombre simple de la clase del componente seguido de[]
. Esto tiene el efecto secundario divertido / extraño que las clases de matriz cuyo tipo de componente es una clase anónima tienen solo[]
como sus nombres simples.
Es interesante observar que getCanonicalName()
y getSimpleName()
pueden generar InternalError
cuando el nombre de la clase tiene un formato incorrecto. Esto sucede con algunos lenguajes JVM que no son Java, por ejemplo, Scala.
Considere lo siguiente (Scala 2.11 en 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
Esto puede ser un problema para entornos de lenguaje mixto o entornos que cargan dinámicamente el código de bytes, por ejemplo, servidores de aplicaciones y otro software de plataforma.
Si no está seguro de algo, intente escribir una prueba primero.
Hice esto:
//primitive
System.out.println(int.class.getName());
System.out.println(int.class.getCanonicalName());
System.out.println(int.class.getSimpleName());
System.out.println();
//class
System.out.println(String.class.getName());
System.out.println(String.class.getCanonicalName());
System.out.println(String.class.getSimpleName());
System.out.println();
//inner class
System.out.println(HashMap.SimpleEntry.class.getName());
System.out.println(HashMap.SimpleEntry.class.getCanonicalName());
System.out.println(HashMap.SimpleEntry.class.getSimpleName());
System.out.println();
//anonymous inner class
System.out.println(new Serializable(){}.getClass().getName());
System.out.println(new Serializable(){}.getClass().getCanonicalName());
System.out.println(new Serializable(){}.getClass().getSimpleName());
Huellas dactilares:
int int int java.lang.String java.lang.String String java.util.AbstractMap$SimpleEntry java.util.AbstractMap.SimpleEntry SimpleEntry ClassnameTest$1 null
Hay una línea vacía en el último bloque donde getSimpleName
devuelve una cadena vacía.
El resultado de esto es:
- el nombre es el nombre que usaría para cargar dinámicamente la clase con, por ejemplo, una llamada a
Class.forName
con elClassLoader
predeterminado. - el nombre canónico es el nombre que se usaría en una declaración de importación e identifica de forma única la clase. Podría ser útil durante las operaciones de
toString
o logging. - el nombre simple identifica la clase de forma flexible, de nuevo, puede ser útil durante las operaciones de registro o registro, pero no se garantiza que sea único.
También me confundió la amplia gama de diferentes esquemas de nombres, y estaba a punto de preguntar y responder a mi propia pregunta sobre esto cuando encontré esta pregunta aquí. Creo que mis hallazgos encajan bastante bien y complementan lo que ya está aquí. Mi objetivo es buscar documentación sobre los diversos términos y agregar algunos términos más relacionados que puedan surgir en otros lugares.
Considere el siguiente ejemplo:
package a.b;
class C {
static class D extends C {
}
D d;
D[] ds;
}
El nombre simple de
D
esD
Esa es la parte que escribiste al declarar la clase. Las clases anónimas no tienen un nombre simple.Class.getSimpleName()
devuelve este nombre o la cadena vacía. Es posible que el nombre simple contenga un$
si lo escribe así, ya que$
es una parte válida de un identificador.Según la sección 6.7 de JLS , tanto
abCD
comoabCDDD
serían nombres completamente calificados , pero soloabCD
sería el nombre canónico deD
Así que cada nombre canónico es un nombre completo, pero los convergentes no siempre son verdaderos.Class.getCanonicalName()
devolverá el nombre canónico onull
.Se documenta que
Class.getName()
devuelve el nombre binario , como se especifica en la sección 13.1 de JLS . En este caso, devuelveabC$D
paraD
y[La.bC$D;
paraD[]
.Esta respuesta demuestra que es posible que dos clases cargadas por el mismo cargador de clases tengan el mismo nombre canónico pero distintos nombres binarios . Ningún nombre es suficiente para deducir de manera confiable el otro: si tiene el nombre canónico, no sabe qué partes del nombre son paquetes y cuáles contienen clases. Si tiene el nombre binario, no sabe qué
$
se introdujeron como separadores y cuáles eran parte de un nombre simple.Las clases anónimas y las clases locales no tienen nombres completos pero aún tienen un nombre binario . Lo mismo se aplica a las clases anidadas dentro de tales clases. Cada clase tiene un nombre binario.
Ejecutar
javap -v -private
ena/b/C.class
muestra que el bytecode se refiere al tipo ded
comoLa/b/C$D;
y el de la matrizds
como[La/b/C$D;
. Estos se denominan descriptores y se especifican en la sección 4.3 de JVMS .El nombre de clase
a/b/C$D
utilizado en estos dos descriptores es lo que obtiene al reemplazarlo.
por/
en el nombre binario. La especificación JVM aparentemente llama a esto la forma interna del nombre binario . La sección 4.2.1 de JVMS lo describe, e indica que la diferencia con el nombre binario fue por razones históricas.El nombre de archivo de una clase en uno de los cargadores de clases típicos basados en nombres de archivo es lo que obtiene si interpreta la
/
en la forma interna del nombre binario como un separador de directorio, y le agrega la extensión de nombre de archivo.class
. Se resuelve en relación con la ruta de clase utilizada por el cargador de clases en cuestión.
este es el mejor documento que encontré describiendo 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[]
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