¿Cuál es la diferencia entre nombre canónico, nombre simple y nombre de clase en Java Class?


973

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 cómo se llaman estos métodos.



218
Creo que esta es una pregunta razonable. El javadoc no hace un buen trabajo al explicar la diferencia entre los tres.
Graham Borland

1
Consulte: docs.oracle.com/javase/6/docs/api/java/lang/Class.html o tal vez simplemente escriba una prueba.
Nick Holt

77
@GrahamBorland El javadoc dice "según lo definido por la Especificación del lenguaje Java" , por lo que puede buscarlo en ese documento. Solo porque no es un enlace en el que se pueda hacer clic, las personas pueden hacer un esfuerzo mínimo y hacer clic en el primer resultado del motor de búsqueda.
vbence

66
@vbence: La mayoría de las personas preferiría hacer las cosas antes que buscar en el JLS cosas triviales como esta. Por lo tanto, este es el primer resultado de Google :)
pathikrit

Respuestas:


1130

Si no está seguro acerca de algo, intente escribir una prueba primero.

Hice esto:

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

Huellas dactilares:

int.class (primitivo):
    getName (): int
    getCanonicalName (): int
    getSimpleName (): int
    getTypeName (): int

String.class (clase ordinaria):
    getName (): java.lang.String
    getCanonicalName (): java.lang.String
    getSimpleName (): String
    getTypeName (): java.lang.String

java.util.HashMap.SimpleEntry.class (clase anidada):
    getName (): java.util.AbstractMap $ SimpleEntry
    getCanonicalName (): java.util.AbstractMap.SimpleEntry
    getSimpleName (): SimpleEntry
    getTypeName (): java.util.AbstractMap $ SimpleEntry

new java.io.Serializable () {}. getClass () (clase interna anónima):
    getName (): ClassNameTest $ 1
    getCanonicalName (): nulo
    getSimpleName ():    
    getTypeName (): ClassNameTest $ 1

Hay una entrada vacía en el último bloque donde getSimpleNamedevuelve una cadena vacía.

El resultado final es:

  • el nombre es el nombre que usaría para cargar dinámicamente la clase con, por ejemplo, una llamada a Class.forNamela predeterminada ClassLoader. Dentro del alcance de un determinado ClassLoader, todas las clases tienen nombres únicos.
  • el nombre canónico es el nombre que se usaría en una declaración de importación. Puede ser útil durante toStringlas operaciones de registro. Cuando el javaccompilador tiene una vista completa de un classpath, impone la unicidad de los nombres canónicos dentro de él al confrontar los nombres de clase y paquete totalmente calificados en tiempo de compilación. Sin embargo, las JVM deben aceptar tales conflictos de nombres y, por lo tanto, los nombres canónicos no identifican de forma exclusiva las clases dentro de a ClassLoader. (En retrospectiva, habría sido un mejor nombre para este getter getJavaName; pero este método data de una época en que la JVM se usaba únicamente para ejecutar programas Java).
  • el nombre simple identifica libremente la clase, nuevamente podría ser útil durante toStringlas operaciones de registro o, pero no se garantiza que sea único.
  • el nombre del tipo devuelve "una cadena informativa para el nombre de este tipo", "Es como toString (): es puramente informativo y no tiene valor de contrato" (como lo escribió sir4ur0n)

55
¿Qué extra crees que se necesita?
Nick Holt

2
@AnupamSaini sí. Tener ese nombre de paquete en una aplicación real sería una locura.
Jayen

3
Sería una locura, sin embargo, ese es el tipo de suposición que permitiría que un actor malicioso funcione. Alguien que dice "oh, bueno, sabemos que las clases nunca comenzarán con minúsculas / los paquetes nunca comenzarán con mayúsculas". De acuerdo, un actor malicioso que tiene acceso a su cargador de clases ya puede hacer cosas terribles, por lo que probablemente no sea una suposición absolutamente terrible.
corsiKa

2
@PieterDeBie ¿Cómo es eso? Todo lo que necesita saber es el nombre del método que desea probar.
fool4jesus

20
Java 8 agregó getTypeName () también ... ¿quieres actualizar para eso?
Theodore Murdock

90

Agregar clases locales, lambdas y el toString()método para completar las dos respuestas anteriores. Además, agrego matrices de lambdas y matrices de clases anónimas (que no tienen ningún sentido en la práctica):

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 {}

Este es el resultado completo:

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 los tipos primitivos y void:

  1. Si el objeto de clase representa un tipo primitivo o void, los cuatro métodos simplemente devuelven su nombre.

Ahora las reglas para el getName()método:

  1. Cada clase o interfaz que no sea lambda ni 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 hay un paquete ), seguido del nombre de su archivo de clase generado por el compilador (sin el sufijo .class). Si no hay paquete, es simplemente el nombre del archivo de clase. Si la clase es una clase interna, anidada, local o anónima, el compilador debe generar al menos una $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.
  2. Los nombres de clase de Lambda son generalmente impredecibles, y de todos modos no debería preocuparse por ellos. Exactamente, su nombre es el nombre de la clase adjunta, seguido de $$Lambda$, seguido de un número, seguido de una barra inclinada, seguido de otro número.
  3. El descriptor de clase de las primitivas son Zpara boolean, Bpara byte, Spara short, Cpara char, Ipara int, Jpara long, Fpara floaty Dpara double. Para las clases y las interfaces que no son matrices, el descriptor de la clase es Lseguido por lo que viene getName()seguido por ;. Para las clases de matriz, el descriptor de clase es [seguido por el descriptor de clase del tipo de componente (que puede ser otra clase de matriz).
  4. Para las clases de matriz, el getName()método devuelve su descriptor de clase. Esta regla parece fallar solo para las clases de matriz cuyo tipo de componente es una lambda (que posiblemente sea un error), pero con suerte esto no debería importar de todos modos porque no tiene sentido incluso la existencia de clases de matriz cuyo tipo de componente es una lambda.

Ahora, el toString()método:

  1. Si la instancia de clase representa una interfaz (o una anotación, que es un tipo especial de interfaz), los toString()retornos "interface " + getName(). Si es un primitivo, vuelve simplemente getName(). Si es algo más (un tipo de clase, incluso si es bastante raro), regresa "class " + getName().

El getCanonicalName()método:

  1. Para las clases e interfaces de nivel superior, el getCanonicalName()método devuelve exactamente lo getName()que devuelve el método.
  2. El getCanonicalName()método devuelve nullpara clases anónimas o locales y para clases de matriz de esos.
  3. Para las clases e interfaces internas y anidadas, el getCanonicalName()método devuelve lo getName()que reemplazaría los signos de dólar introducidos por el compilador por puntos.
  4. Para las clases de matriz, el getCanonicalName()método devuelve nullsi el nombre canónico del tipo de componente es null. De lo contrario, devuelve el nombre canónico del tipo de componente seguido de [].

El getSimpleName()método:

  1. Para las clases de nivel superior, anidadas, internas y locales, getSimpleName()devuelve el nombre de la clase tal como está escrito en el archivo fuente.
  2. Para clases anónimas, getSimpleName()devuelve un vacío String.
  3. Para las clases lambda, el getSimpleName()just devuelve lo getName()que devolvería sin el nombre del paquete. Esto no tiene mucho sentido y parece un error para mí, pero no tiene sentido llamar getSimpleName()a una clase lambda para empezar.
  4. Para las clases de matriz, el getSimpleName()método devuelve el nombre simple de la clase de 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 []como nombres simples.

2
… replacing the dollar-signs by dots: Solo se están reemplazando los signos de dólar que se introdujeron como delimitadores. Bien puede tener dólares como parte de un nombre simple, y esos permanecerán en su lugar.
MvG

¡Oh no! Como parte del nombre de la clase! Estoy desarrollando un transformador de clase y pensé que '/' sería un delimitador seguro entre la clase y el nombre del paquete: /
José Roberto Araújo Júnior

81

Además de las observaciones de Nick Holt, ejecuté algunos casos para Arrayel tipo de datos:

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

Impresiones de fragmentos de código anteriores:

[I
int[]
int[]

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

28
No sería mucho mejor proponer una edición a la respuesta anterior.
LoKi

17

También me ha confundido la amplia gama de diferentes esquemas de nombres, y estaba a punto de preguntar y responder mi propia pregunta sobre esto cuando encontré esta pregunta aquí. Creo que mis hallazgos encajan lo suficientemente bien y complementan lo que ya está aquí. Mi enfoque es buscar documentación sobre los diversos términos y agregar algunos términos más relacionados que podrían surgir en otros lugares.

Considere el siguiente ejemplo:

package a.b;
class C {
  static class D extends C {
  }
  D d;
  D[] ds;
}
  • El simple nombre de Des D. Esa es solo 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 3.8 de JLS (incluso si se desaconseja un poco).

  • De acuerdo con la sección 6.7 JLS , tanto a.b.C.Dy a.b.C.D.D.Dsería nombres completos , pero sólo a.b.C.Dsería el nombre canónico de D. Por lo tanto, cada nombre canónico es un nombre totalmente calificado, pero lo contrario no siempre es cierto. Class.getCanonicalName()devolverá el nombre canónico o null.

  • Class.getName()está documentado para devolver el nombre binario , tal como se especifica en la sección 13.1 de JLS . En este caso, devuelve a.b.C$Dpor Dy [La.b.C$D;para D[].

  • Esta respuesta demuestra que es posible que dos clases cargadas por el mismo cargador de clases tengan el mismo nombre canónico pero nombres binarios distintos . Ninguno de los dos nombres 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 cuáles $se introdujeron como separadores y cuáles fueron parte de un nombre simple. (El archivo de clase almacena el nombre binario de la clase en sí y su clase adjunta , lo que permite que el tiempo de ejecución haga esta distinción ).

  • Las clases anónimas y las clases locales no tienen nombres completos, pero aún tienen un nombre binario . Lo mismo vale para las clases anidadas dentro de tales clases. Cada clase tiene un nombre binario.

  • Correr javap -v -privateen a/b/C.classmuestra que el código de bytes se refiere al tipo de dcomo La/b/C$D;y el de la matriz dscomo [La/b/C$D;. Estos se denominan descriptores y se especifican en la sección 4.3 de JVMS .

  • El nombre de la clase a/b/C$Dutilizada en estos dos descriptores es lo que se obtiene mediante la sustitución .por /el nombre del archivo 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 y establece que la diferencia con el nombre binario se debe a razones históricas.

  • El nombre de archivo de una clase en uno de los cargadores de clases típicos basados ​​en nombre de archivo es lo que obtienes si interpretas el /en forma interna del nombre binario como un separador de directorio y le agregas 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.


3
Esta debería ser la respuesta aceptada, ya que es la única respuesta que hace referencia al JLS y utiliza las terminologías adecuadas.
John

10

este es el mejor documento que encontré que describe 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

Es interesante notar eso getCanonicalName()y getSimpleName()puede aparecer InternalErrorcuando el nombre de la clase está mal formado. Esto sucede para 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 idiomas mixtos o entornos que cargan dinámicamente bytecode, por ejemplo, servidores de aplicaciones y otro software de plataforma.


2

getName () : devuelve el nombre de la entidad (clase, interfaz, clase de matriz, tipo primitivo o vacío) representada por este objeto de clase, como una cadena.

getCanonicalName () : devuelve el nombre canónico de la clase subyacente tal como se define en la Especificación del lenguaje Java.

getSimpleName () : devuelve el nombre simple de la clase subyacente, es decir, el nombre que se le ha dado en el código fuente.

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 
  }
}

Una diferencia es que si usa una clase anónima, puede obtener un valor nulo al intentar obtener el nombre de la clase usandogetCanonicalName()

Otro hecho es que el getName()método se comporta de manera diferente que el getCanonicalName()método para las clases internas . getName()usa un dólar como separador entre el nombre canónico de la clase que lo encierra y el nombre simple de la clase interna.

Para saber más sobre cómo recuperar un nombre de clase 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
Las primeras dos líneas dentro del método se pueden reducir aClass<StringBuffer> clazz = StringBuffer.class
ThePyroEagle el
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.