Jaka jest różnica między nazwą kanoniczną, nazwą prostą i nazwą klasy w Javie Class?

972

W Javie jaka jest różnica między nimi:

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

Sprawdziłem Javadoc wiele razy, ale to nigdy nie wyjaśnia tego dobrze. Przeprowadziłem również test, który nie odzwierciedlał żadnego rzeczywistego znaczenia w sposobie wywoływania tych metod.

Mohamed Taher Alrefaie
źródło
218
Myślę, że to rozsądne pytanie. Jawadok nie radzi sobie dobrze z wyjaśnieniem różnicy między tymi trzema.
Graham Borland
1
Zobacz - docs.oracle.com/javase/6/docs/api/java/lang/Class.html, a może po prostu napisz test.
Nick Holt
7
@GrahamBorland Jawadok mówi „zgodnie ze specyfikacją języka Java” - abyś mógł to sprawdzić w tym dokumencie. Tylko dlatego, że nie jest to klikalny link, ludzie mogą w dalszym ciągu minimalizować wysiłek i kliknąć wynik pierwszej wyszukiwarki.
vbence
66
@vbence: Większość ludzi wolałaby załatwić sprawę niż szukać JLS pod kątem takich trywialnych rzeczy. Dlatego jest to pierwszy wynik Google :)
pathikrit

Odpowiedzi:

1129

Jeśli nie masz pewności, spróbuj najpierw napisać test.

Ja to zrobiłem:

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

Wydruki:

int. klasa (prymitywna):
    getName (): int
    getCanonicalName (): int
    getSimpleName (): int
    getTypeName (): int

String.class (zwykła klasa):
    getName (): java.lang.String
    getCanonicalName (): java.lang.String
    getSimpleName (): String
    getTypeName (): java.lang.String

java.util.HashMap.SimpleEntry.class (klasa zagnieżdżona):
    getName (): java.util.AbstractMap $ SimpleEntry
    getCanonicalName (): java.util.AbstractMap.SimpleEntry
    getSimpleName (): SimpleEntry
    getTypeName (): java.util.AbstractMap $ SimpleEntry

new java.io.Serializable () {}. getClass () (anonimowa klasa wewnętrzna):
    getName (): ClassNameTest $ 1
    getCanonicalName (): null
    getSimpleName ():    
    getTypeName (): ClassNameTest $ 1

W ostatnim bloku jest pusty wpis, w którym getSimpleNamezwraca pusty ciąg.

Efektem tego jest:

  • nazwa jest nazwą byłoby użyć dynamicznie załadować klasę z, na przykład, do połączenia Class.forNamez domyślnym ClassLoader. W ramach pewnego ClassLoader, wszystkie klasy mają unikalne nazwy.
  • nazwa kanoniczna to nazwa, która będzie używana w instrukcji import. Może to być przydatne podczas toStringoperacji lub logowania. Gdy javackompilator ma pełny widok ścieżki klasy, wymusza unikatowość nazw kanonicznych w nim, zderzając w pełni kwalifikowane nazwy klas i pakietów w czasie kompilacji. Jednak maszyny JVM muszą zaakceptować takie konflikty nazw, a zatem nazwy kanoniczne nie jednoznacznie identyfikują klasy w obrębie ClassLoader. (Z perspektywy czasu lepsza nazwa tego programu pobierającego byłaby getJavaName; ale ta metoda pochodzi z czasów, gdy JVM był używany wyłącznie do uruchamiania programów Java).
  • nazwa prosta luźno określa klasę, znowu może być przydatna podczas toStringlub operacji rejestrowania ale nie są gwarantowane być unikalne.
  • to nazwa typu zwraca „ciąg informacyjny dla nazwy tego typu”, „To jest jak toString (): jest czysto informacyjny i nie ma wartości umowy” (jak napisał sir4ur0n)
Nick Holt
źródło
5
Jak myślisz, jakie dodatkowe są potrzebne?
Nick Holt
2
@AnupamSaini tak. Posiadanie takiej nazwy pakietu w prawdziwej aplikacji byłoby szalone.
Jayen
3
IT byłoby szalone, jednak takie założenie pozwoliłoby złośliwemu aktorowi na działanie. Ktoś mówi „och, dobrze wiemy, że klasy nigdy nie zaczną się małymi skrzynkami / pakietami nigdy nie zaczną się wielkimi literami”. To prawda, złośliwy aktor, który ma dostęp do twojego modułu ładującego klasy, może już robić straszne rzeczy, więc prawdopodobnie nie jest to absolutnie straszne założenie.
corsiKa
2
@PieterDeBie Jak to zrobić? Wszystko, co musisz wiedzieć, to nazwa metody, którą chcesz przetestować.
fool4jesus,
20
Java 8 dodała również getTypeName () ... czy chcesz to zaktualizować?
Theodore Murdock
90

Dodanie klas lokalnych, lambdów i toString()metody uzupełniania dwóch poprzednich odpowiedzi. Ponadto dodaję tablice lambdas i tablice anonimowych klas (które nie mają jednak żadnego sensu w praktyce):

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

To jest pełny wynik:

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;

Oto zasady. Najpierw zacznijmy od typów pierwotnych i void:

  1. Jeśli obiekt klasy reprezentuje typ pierwotny lub void, wszystkie cztery metody po prostu zwracają jego nazwę.

Teraz zasady dla getName()metody:

  1. Każda klasa lub interfejs niebędący lambda i niebędący tablicą (tj. Najwyższego poziomu, zagnieżdżony, wewnętrzny, lokalny i anonimowy) ma nazwę (zwracaną przez getName()), która jest nazwą pakietu, po której następuje kropka (jeśli istnieje pakiet ), a po nim nazwa pliku klasy wygenerowanego przez kompilator (bez sufiksu .class). Jeśli nie ma pakietu, jest to po prostu nazwa pliku klasy. Jeśli klasa jest klasą wewnętrzną, zagnieżdżoną, lokalną lub anonimową, kompilator powinien wygenerować co najmniej jedną $w swojej nazwie pliku klasy. Pamiętaj, że w przypadku klas anonimowych nazwa klasy kończy się znakiem dolara, po którym następuje cyfra.
  2. Nazwy klas Lambda są na ogół nieprzewidywalne i i tak nie powinieneś się tym przejmować. Dokładnie, ich nazwa to nazwa otaczającej klasy, po której $$Lambda$następuje liczba, po której następuje ukośnik, a następnie kolejny numer.
  3. Klasa Deskryptorem prymitywów są Zza boolean, Bza byte, Sza short, Cza char, Iza int, Jza long, Fza floati Ddla double. W przypadku klas i interfejsów niebędących tablicami po deskryptorze klasy Lnastępuje, po czym getName()następuje ;. W przypadku klas tablic po deskryptorze klasy [następuje deskryptor klasy typu komponentu (który może być inną klasą tablicy).
  4. W przypadku klas tablic getName()metoda zwraca deskryptor klasy. Ta reguła wydaje się zawodzić tylko w przypadku klas tablic, których typem komponentu jest lambda (co prawdopodobnie jest błędem), ale mam nadzieję, że to i tak nie powinno mieć znaczenia, ponieważ nie ma sensu nawet istnienie klas tablic, których typem komponentu jest lambda.

Teraz toString()metoda:

  1. Jeśli instancja klasy reprezentuje interfejs (lub adnotację, która jest specjalnym typem interfejsu), toString()zwraca "interface " + getName(). Jeśli jest prymitywny, zwraca po prostu getName(). Jeśli jest to coś innego (typ klasy, nawet jeśli jest dość dziwny), zwraca "class " + getName().

getCanonicalName()Metoda:

  1. W przypadku klas i interfejsów najwyższego poziomu getCanonicalName()metoda zwraca tylko to, co getName()zwraca metoda.
  2. Te getCanonicalName()metoda powraca nulldo anonimowego lub lokalnych klas i dla klas tablicy z nich.
  3. Dla wewnętrznych i zagnieżdżonych klas i interfejsów getCanonicalName()metoda zwraca to, co getName()metoda zastąpiłaby znaki dolara wprowadzone przez kompilator kropkami.
  4. W przypadku klas tablic getCanonicalName()metoda zwraca, nulljeśli nazwa kanoniczna typu komponentu to null. W przeciwnym razie zwraca kanoniczną nazwę typu komponentu, po której następuje [].

getSimpleName()Metoda:

  1. W przypadku klas najwyższego poziomu, zagnieżdżonych, wewnętrznych i lokalnych getSimpleName()zwraca nazwę klasy zapisaną w pliku źródłowym.
  2. Dla klas anonimowych getSimpleName()zwraca pusty String.
  3. Dla klas lambda po getSimpleName()prostu zwraca to, co getName()zwróci bez nazwy pakietu. To nie ma większego sensu i wygląda na błąd, ale nie ma sensu wzywać getSimpleName()klasy lambda na początek.
  4. W przypadku klas tablic getSimpleName()metoda zwraca prostą nazwę klasy komponentu, po której następuje []. Ma to zabawny / dziwny efekt uboczny, jaki mają takie []proste nazwy klas tablic, których typem składowym jest klasa anonimowa .
Victor Stafusa
źródło
2
… replacing the dollar-signs by dots: Zastępowane są tylko znaki dolara wprowadzone jako ograniczniki. Możesz mieć dolary jako część prostej nazwy, a te pozostaną na swoim miejscu.
MvG
O nie! Jako część nazwy klasy! Opracowuję transformator klasy i pomyślałem, że „/” będzie bezpiecznym separatorem między klasą a nazwą pakietu: /
José Roberto Araújo Júnior
81

Oprócz obserwacji Nicka Holta, prowadziłem kilka spraw dotyczących Arraytypu danych:

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

Wydruki kodu powyżej:

[I
int[]
int[]

[Ljava.lang.Integer;
java.lang.Integer[]
Integer[]
gerardw
źródło
28
Nie byłoby o wiele lepiej zaproponować zmianę powyższej odpowiedzi.
LoKi
17

Byłem zdezorientowany szeroką gamą różnych schematów nazewnictwa i właśnie miałem zadać i odpowiedzieć na moje własne pytanie na ten temat, kiedy znalazłem to pytanie tutaj. Myślę, że moje ustalenia pasują wystarczająco dobrze i uzupełniają to, co już jest. Skupiam się na dokumentacji różnych terminów i dodaniu kolejnych powiązanych terminów, które mogą pojawić się w innych miejscach.

Rozważ następujący przykład:

package a.b;
class C {
  static class D extends C {
  }
  D d;
  D[] ds;
}
  • Prosta nazwa z Dto D. To tylko część, którą napisałeś, deklarując klasę. Anonimowe klasy nie mają prostej nazwy. Class.getSimpleName()zwraca tę nazwę lub pusty ciąg. Prosta nazwa może zawierać a, $jeśli napiszesz ją w ten sposób, ponieważ $jest to poprawna część identyfikatora zgodnie z sekcją 3.8 JLS (nawet jeśli jest to nieco odradzane).

  • Według sekcji JLS 6.7 , zarówno a.b.C.Di a.b.C.D.D.Dbędzie w pełni kwalifikowane nazwy , ale tylko a.b.C.Dbyłby kanoniczna nazwa od D. Każda nazwa kanoniczna jest więc nazwą w pełni kwalifikowaną, ale odwrotność nie zawsze jest prawdziwa. Class.getCanonicalName()zwróci nazwę kanoniczną lub null.

  • Class.getName()jest udokumentowany, aby zwrócić nazwę binarną , jak określono w sekcji 13.1 JLS . W tym przypadku zwraca a.b.C$Dza Di [La.b.C$D;za D[].

  • Ta odpowiedź pokazuje, że dwie klasy ładowane przez ten sam moduł ładujący klasy mogą mieć tę samą nazwę kanoniczną, ale różne nazwy binarne . Żadna z tych nazw nie jest wystarczająca do wiarygodnego wywnioskowania drugiej: jeśli masz kanoniczną nazwę, nie wiesz, które części nazwy są paczkami, a które zawierają klasy. Jeśli masz nazwę binarną, nie wiesz, które $zostały wprowadzone jako separatory, a które były częścią jakiejś prostej nazwy. (Plik klasy przechowuje binarne nazwa z samej klasy i jej klasy załączając , który pozwala wykonawcze do dokonać tego rozróżnienia ).

  • Klasy anonimowe i klasy lokalne nie mają w pełni kwalifikowanych nazw, ale nadal mają nazwy binarne . To samo dotyczy klas zagnieżdżonych w takich klasach. Każda klasa ma nazwę binarną.

  • Działa javap -v -privatena a/b/C.classpokazuje, że bajtowy odnosi się do typu d, jak La/b/C$D;i matrycy ds, jak [La/b/C$D;. Są to tak zwane deskryptory i są określone w sekcji 4.3 JVMS .

  • Nazwa klasy a/b/C$Dużywana w obu tych deskryptorach jest otrzymywana przez zastąpienie .przez /w nazwie binarnej. Specyfikacja JVM najwyraźniej nazywa to wewnętrzną formą nazwy binarnej . JVMS sekcja 4.2.1 opisuje to i stwierdza, że ​​różnica od nazwy binarnej wynikała z przyczyn historycznych.

  • Nazwa pliku klasy w jednej z typowych ładowarki filename class oparte na to co masz, jeśli interpretować /w wewnętrznej formie binarnej nazwy jako separator katalogów i dołączyć rozszerzenie nazwy pliku .classdo niego. Jest rozwiązany względem ścieżki klasy używanej przez moduł ładujący, o którym mowa.

MvG
źródło
3
To powinna być zaakceptowana odpowiedź, ponieważ jest to jedyna odpowiedź, która odwołuje się do JLS i używa właściwych terminologii.
John
10

to najlepszy dokument, który znalazłem opisujący 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[]
Kiran
źródło
3

Interesujące jest to, aby pamiętać, że getCanonicalName()i getSimpleName()może podnieść InternalErrorgdy nazwa klasy jest niepoprawny. Dzieje się tak w przypadku niektórych języków JVM innych niż Java, np. Scala.

Rozważ następujące kwestie (Scala 2.11 w Javie 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

Może to stanowić problem w środowiskach mieszanych lub środowiskach, które dynamicznie ładują kod bajtowy, np. Serwery aplikacji i inne oprogramowanie platformy.

Sim
źródło
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
Shirish Singh
źródło
1
Pierwsze dwie linie w metodzie można sprowadzić doClass<StringBuffer> clazz = StringBuffer.class
ThePyroEagle,
1

getName () - zwraca nazwę bytu (klasa, interfejs, klasa tablicowa, typ pierwotny lub void) reprezentowana przez ten obiekt klasy jako ciąg.

getCanonicalName () - zwraca kanoniczną nazwę klasy bazowej zdefiniowanej w specyfikacji języka Java.

getSimpleName () - zwraca prostą nazwę klasy bazowej, to jest nazwę, którą podano w kodzie źródłowym.

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

Jedną różnicą jest to, że jeśli używasz anonimowej klasy , możesz uzyskać wartość zerową podczas próby uzyskania nazwy klasy za pomocągetCanonicalName()

Innym faktem jest to, że getName()metoda zachowuje się inaczej niż getCanonicalName()metoda klas wewnętrznych . getName()używa dolara jako separatora między kanoniczną nazwą klasy zamykającej a prostą nazwą klasy wewnętrznej.

Aby dowiedzieć się więcej na temat pobierania nazwy klasy w Javie .

Abdul Alim Shakir
źródło