Qual è la differenza tra nome canonico, nome semplice e nome della classe in Java Class?


973

In Java, qual è la differenza tra questi:

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

Ho controllato più volte Javadoc e tuttavia questo non lo spiega mai bene. Ho anche eseguito un test e questo non rifletteva alcun significato reale dietro il modo in cui vengono chiamati questi metodi.



218
Penso che questa sia una domanda ragionevole. Il javadoc non fa un buon lavoro nel spiegare la differenza tra i tre.
Graham Borland

1
Vedi - docs.oracle.com/javase/6/docs/api/java/lang/Class.html o forse basta scrivere un test.
Nick Holt,

7
@GrahamBorland Il javadoc dice "come definito da Java Language Specification" - in modo da poterlo cercare in quel documento. Solo perché non è un link cliccabile, le persone possono ancora fare uno sforzo minimo e fare clic sul primo risultato del motore di ricerca.
vbence,

66
@vbence: la maggior parte delle persone preferirebbe fare le cose piuttosto che cercare JLS per cose banali come questa. Quindi, questo è il primo risultato di Google :)
pathikrit

Risposte:


1130

Se non sei sicuro di qualcosa, prova prima a scrivere un test.

L'ho fatto:

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

stampe:

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

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

java.util.HashMap.SimpleEntry.class (classe nidificata):
    getName (): java.util.AbstractMap $ SimpleEntry
    getCanonicalName (): java.util.AbstractMap.SimpleEntry
    getSimpleName (): SimpleEntry
    getTypeName (): java.util.AbstractMap $ SimpleEntry

new java.io.Serializable () {}. getClass () (classe interna anonima):
    getName (): ClassNameTest $ 1
    getCanonicalName (): null
    getSimpleName ():    
    getTypeName (): ClassNameTest $ 1

C'è una voce vuota nell'ultimo blocco in cui getSimpleNamerestituisce una stringa vuota.

Il risultato di questo è:

  • il nome è il nome che useresti per caricare dinamicamente la classe con, ad esempio, una chiamata Class.forNamecon il valore predefinito ClassLoader. Nell'ambito di un determinato criterio ClassLoader, tutte le classi hanno nomi univoci.
  • il nome canonico è il nome che verrebbe utilizzato in un'istruzione import. Potrebbe essere utile durante toStringo durante le operazioni di registrazione. Quando il javaccompilatore ha una visione completa di un percorso di classe, impone l'unicità di nomi canonici al suo interno scontrandosi con nomi di classi e pacchetti completi al momento della compilazione. Tuttavia, le JVM devono accettare tali conflitti di nomi e quindi i nomi canonici non identificano in modo univoco le classi all'interno di a ClassLoader. (Con il senno di poi, sarebbe stato un nome migliore per questo getter getJavaName; ma questo metodo risale a un momento in cui la JVM veniva utilizzata esclusivamente per eseguire i programmi Java.)
  • il nome semplice identifica vagamente la classe, potrebbe essere utile nuovamente durante toStringo durante le operazioni di registrazione ma non è garantito che sia univoco.
  • il nome del tipo restituisce "una stringa informativa per il nome di questo tipo", "È come toString (): è puramente informativo e non ha valore contrattuale" (come scritto da sir4ur0n)

5
Quale extra pensi sia necessario?
Nick Holt

2
@AnupamSaini sì. Avere un nome del pacchetto simile in una vera applicazione sarebbe folle.
Jayen,

3
L'IT sarebbe folle, tuttavia, questo è il tipo di presupposto che consentirebbe a un attore malintenzionato di lavorare. Qualcuno che dice "oh, ben sappiamo che le lezioni non inizieranno mai con lettere minuscole / i pacchetti non inizieranno mai con le maiuscole". Certo, un attore malintenzionato che ha accesso al tuo caricatore di classi può già fare cose terribili, quindi probabilmente non è un presupposto assolutamente terribile.
corsiKa

2
@PieterDeBie In che modo? Tutto quello che devi sapere è il nome del metodo che vuoi testare.
fool4jesus,

20
Java 8 ha aggiunto anche getTypeName () ... ti va di aggiornarlo?
Theodore Murdock,

90

Aggiunta di classi locali, lambda e toString()metodo per completare le due risposte precedenti. Inoltre, aggiungo array di lambda e array di classi anonime (che in pratica non hanno alcun senso):

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

Questo è l'output 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;

Quindi, ecco le regole. Innanzitutto, iniziamo con tipi primitivi e void:

  1. Se l'oggetto class rappresenta un tipo primitivo o void, tutti e quattro i metodi restituiscono semplicemente il suo nome.

Ora le regole per il getName()metodo:

  1. Ogni classe o interfaccia non lambda e non array (ad esempio, di livello superiore, nidificato, interno, locale e anonimo) ha un nome (che viene restituito da getName()) che è il nome del pacchetto seguito da un punto (se esiste un pacchetto ), seguito dal nome del suo file di classe generato dal compilatore (senza il suffisso .class). Se non esiste un pacchetto, è semplicemente il nome del file di classe. Se la classe è una classe interna, nidificata, locale o anonima, il compilatore dovrebbe generarne almeno una $nel nome del suo file di classe. Nota che per le classi anonime, il nome della classe terminerebbe con un segno di dollaro seguito da un numero.
  2. I nomi delle classi Lambda sono generalmente imprevedibili e non dovresti preoccupartene comunque. Esatto, il loro nome è il nome della classe allegata, seguito da $$Lambda$, seguito da un numero, seguito da una barra, seguito da un altro numero.
  3. Il descrittore di classe dei primitivi è Zper boolean, Bper byte, Sper short, Cper char, Iper int, Jper long, Fper floate Dper double. Per le classi e le interfacce non array il descrittore di classe è Lseguito da ciò che è dato da getName()seguito da ;. Per le classi di array, il descrittore di classe è [seguito dal descrittore di classe del tipo di componente (che può essere esso stesso un'altra classe di array).
  4. Per le classi di array, il getName()metodo restituisce il descrittore di classe. Questa regola sembra fallire solo per le classi di array il cui tipo di componente è un lambda (che probabilmente è un bug), ma si spera che questo non dovrebbe importare comunque perché non ha senso nemmeno l'esistenza delle classi di array il cui tipo di componente è un lambda.

Ora, il toString()metodo:

  1. Se l'istanza della classe rappresenta un'interfaccia (o un'annotazione, che è un tipo speciale di interfaccia), i toString()ritorni"interface " + getName() . Se è un primitivo, ritorna semplicemente getName(). Se è qualcos'altro (un tipo di classe, anche se piuttosto strano), ritorna "class " + getName().

Il getCanonicalName()metodo:

  1. Per le classi e le interfacce di livello superiore, il getCanonicalName() metodo restituisce esattamente ciò getName()che restituisce il metodo.
  2. Il getCanonicalName() metodo ritorna nullper le classi anonime o locali e per le classi di array di quelle.
  3. Per le classi e le interfacce interne e nidificate, il getCanonicalName() metodo restituisce ciò che il getName()metodo sostituirebbe i segni del dollaro introdotti dal compilatore con punti.
  4. Per le classi di array, il getCanonicalName()metodo restituisce nullse il nome canonico del tipo di componente è null. Altrimenti, restituisce il nome canonico del tipo di componente seguito da[] .

Il getSimpleName()metodo:

  1. Per le classi di livello superiore, nidificate, interne e locali, il getSimpleName() restituisce il nome della classe come scritto nel file di origine.
  2. Per le classi anonime il getSimpleName() restituisce un vuoto String.
  3. Per le classi lambda il getSimpleName()giusto restituisce ciò che ilgetName() verrebbe restituito senza il nome del pacchetto. Questo non ha molto senso e mi sembra un bug, ma non ha senso invocare getSimpleName()una classe lambda per cominciare.
  4. Per le classi di array il getSimpleName()metodo restituisce il nome semplice della classe del componente seguito da []. Questo ha il divertente / strano effetto collaterale che le classi di array il cui tipo di componente è una classe anonima hanno []i loro semplici nomi.

2
… replacing the dollar-signs by dots: Vengono sostituiti solo i simboli di dollaro introdotti come delimitatori. Puoi anche avere dollari come parte di un semplice nome e questi rimarranno al loro posto.
MvG

Oh no! Come parte del nome della classe! Sto sviluppando un trasformatore di classe e ho pensato che '/' sarebbe un delimitatore sicuro tra la classe e il nome del pacchetto: /
José Roberto Araújo Júnior

81

Oltre alle osservazioni di Nick Holt, ho eseguito alcuni casi per il Arraytipo di dati:

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

Stampe sopra lo snippet di codice:

[I
int[]
int[]

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

28
Non sarebbe molto meglio proporre una modifica alla risposta sopra.
LoKi,

17

Sono stato confuso anche dalla vasta gamma di diversi schemi di denominazione, e stavo per fare una domanda e rispondere alla mia domanda su questo quando ho trovato questa domanda qui. Penso che le mie scoperte si adattino abbastanza bene e completino ciò che è già qui. La mia attenzione è la ricerca di documentazione sui vari termini e l'aggiunta di altri termini correlati che potrebbero sorgere in altri luoghi.

Considera il seguente esempio:

package a.b;
class C {
  static class D extends C {
  }
  D d;
  D[] ds;
}
  • Il semplice nome di Dè D. Questa è solo la parte che hai scritto quando hai dichiarato la classe. Le classi anonime non hanno un nome semplice. Class.getSimpleName()restituisce questo nome o la stringa vuota. È possibile che il nome semplice contenga un $se lo si scrive in questo modo, poiché $è una parte valida di un identificatore secondo la sezione 3.8 di JLS (anche se è un po 'scoraggiato).

  • Secondo la sezione 6.7 di JLS , entrambi a.b.C.De a.b.C.D.D.Dsarebbero nomi pienamente qualificati , ma a.b.C.Dsarebbe solo il nome canonico di D. Quindi ogni nome canonico è un nome completo, ma il contrario non è sempre vero. Class.getCanonicalName()restituirà il nome canonico o null.

  • Class.getName()è documentato per restituire il nome binario , come specificato nella sezione 13.1 di JLS . In questo caso ritorna a.b.C$Dper De [La.b.C$D;per D[].

  • Questa risposta dimostra che è possibile che due classi caricate dallo stesso caricatore di classi abbiano lo stesso nome canonico ma nomi binari distinti . Nessuno dei due nomi è sufficiente per dedurre in modo affidabile l'altro: se si dispone del nome canonico, non si conosce quali parti del nome sono pacchetti e quali contengono classi. Se hai il nome binario, non sai quali $sono stati introdotti come separatori e quali facevano parte di un semplice nome. (Il file di classe memorizza il nome binario della classe stessa e della sua classe che lo racchiude , che consente al runtime di fare questa distinzione .)

  • Le classi anonime e le classi locali non hanno nomi completi ma hanno ancora un nome binario . Lo stesso vale per le classi nidificate all'interno di tali classi. Ogni classe ha un nome binario.

  • L'esecuzione javap -v -privatesu a/b/C.classmostra che il bytecode si riferisce al tipo di das La/b/C$D;e a quello dell'array dsas [La/b/C$D;. Questi sono chiamati descrittori e sono specificati nella sezione 4.3 di JVMS .

  • Il nome della classe a/b/C$Dutilizzato in entrambi questi descrittori è quello che ottieni sostituendo .con /il nome binario. Apparentemente le specifiche JVM chiamano questa la forma interna del nome binario . JVMS sezione 4.2.1 lo descrive e afferma che la differenza dal nome binario era per motivi storici.

  • Il nome file di una classe in uno dei tipici caricatori di classi basati su nome file è quello che si ottiene se si interpreta la /forma interna del nome binario come separatore di directory e si aggiunge l'estensione del nome file .class. È stato risolto in relazione al percorso di classe utilizzato dal programma di caricamento classi in questione.


3
Questa dovrebbe essere la risposta accettata poiché è l'unica risposta che fa riferimento a JLS e utilizza terminologie appropriate.
Giovanni

10

questo è il miglior documento che ho trovato descrivendo 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

E 'interessante notare che getCanonicalName()e getSimpleName()può aumentareInternalError quando il nome della classe non è corretto. Questo accade per alcuni linguaggi JVM non Java, ad esempio Scala.

Considera quanto segue (Scala 2.11 su 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

Questo può essere un problema per ambienti di lingua mista o ambienti che caricano dinamicamente bytecode, ad esempio server di app e altri software di piattaforma.


2

getName () - restituisce il nome dell'entità (classe, interfaccia, classe di matrice, tipo primitivo o vuoto) rappresentato da questo oggetto Class, come stringa.

getCanonicalName () - restituisce il nome canonico della classe sottostante come definito da Java Language Specification.

getSimpleName () - restituisce il nome semplice della classe sottostante, ovvero il nome che è stato dato nel codice sorgente.

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 differenza è che se usi una classe anonima puoi ottenere un valore nullo quando provi a ottenere il nome della classe usando ilgetCanonicalName()

Un altro fatto è che il getName()metodo si comporta in modo diverso rispetto al getCanonicalName()metodo per le classi interne . getName()usa un dollaro come separatore tra il nome canonico della classe che lo racchiude e il nome semplice della classe interna.

Per saperne di più sul recupero di un nome di classe in 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
Le prime due righe all'interno del metodo possono essere ridotte aClass<StringBuffer> clazz = StringBuffer.class
ThePyroEagle il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.