Apa perbedaan antara nama kanonik, nama sederhana dan nama kelas dalam Java Class?

973

Di Jawa, apa perbedaan antara ini:

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

Saya telah memeriksa Javadoc beberapa kali, namun ini tidak pernah menjelaskan dengan baik. Saya juga menjalankan tes dan itu tidak mencerminkan makna nyata di balik cara metode ini disebut.

Mohamed Taher Alrefaie
sumber
218
Saya pikir ini adalah pertanyaan yang masuk akal. Javadoc tidak melakukan pekerjaan dengan baik untuk menjelaskan perbedaan antara ketiganya.
Graham Borland
1
Lihat - docs.oracle.com/javase/6/docs/api/java/lang/Class.html atau mungkin hanya menulis tes.
Nick Holt
7
@GrahamBorland javadoc mengatakan "sebagaimana didefinisikan oleh Spesifikasi Bahasa Jawa" - sehingga Anda dapat mencarinya di dokumen itu. Hanya karena ini bukan tautan yang dapat diklik, orang masih dapat melakukan upaya minimal dan mengklik hasil mesin pencari pertama.
vbence
66
@ VBence: Kebanyakan orang lebih suka menyelesaikan sesuatu daripada mencari JLS untuk hal-hal sepele seperti ini. Karenanya, ini adalah hasil Google pertama :)
pathikrit

Jawaban:

1130

Jika Anda tidak yakin tentang sesuatu, cobalah menulis tes terlebih dahulu.

Saya melakukan ini:

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

Cetakan:

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

String.class (kelas biasa):
    getName (): java.lang.String
    getCanonicalName (): java.lang.String
    getSimpleName (): String
    getTypeName (): java.lang.String

java.util.HashMap.SimpleEntry.class (kelas bersarang):
    getName (): java.util.AbstractMap $ SimpleEntry
    getCanonicalName (): java.util.AbstractMap.SimpleEntry
    getSimpleName (): SimpleEntry
    getTypeName (): java.util.AbstractMap $ SimpleEntry

java.io.Serializable baru () {}. getClass () (kelas dalam anonim):
    getName (): ClassNameTest $ 1
    getCanonicalName (): null
    getSimpleName ():    
    getTypeName (): ClassNameTest $ 1

Ada entri kosong di blok terakhir tempat getSimpleNamemengembalikan string kosong.

Hasilnya melihat ini:

  • yang nama adalah nama yang akan Anda gunakan untuk secara dinamis memuat kelas dengan, misalnya, panggilan untuk Class.forNamedengan default ClassLoader. Dalam ruang lingkup tertentu ClassLoader, semua kelas memiliki nama unik.
  • yang nama kanonik adalah nama yang akan digunakan dalam sebuah pernyataan impor. Ini mungkin berguna selama toStringatau operasi logging. Ketika javackompilator memiliki pandangan lengkap tentang classpath, ia menegakkan keunikan nama kanonik di dalamnya dengan berbenturan kelas dan nama paket yang sepenuhnya memenuhi syarat pada waktu kompilasi. Namun JVM harus menerima bentrokan nama tersebut, dan dengan demikian nama kanonik tidak secara unik mengidentifikasi kelas dalam a ClassLoader. (Jika dipikir-pikir, nama yang lebih baik untuk pengambil ini adalah getJavaName; tetapi metode ini berasal dari waktu ketika JVM digunakan hanya untuk menjalankan program Java.)
  • yang nama sederhana longgar mengidentifikasi kelas, lagi mungkin berguna selama toStringoperasi penebangan atau tetapi tidak dijamin untuk menjadi unik.
  • yang jenis nama kembali "string informatif untuk nama jenis ini", "Ini seperti toString (): itu murni informatif dan tidak memiliki nilai kontrak" (seperti yang ditulis oleh sir4ur0n)
Nick Holt
sumber
5
Menurut Anda, apa tambahan yang dibutuhkan?
Nick Holt
2
@AnupamSaini ya. Memiliki nama paket seperti itu dalam aplikasi nyata akan menjadi gila.
Jayen
3
ITU akan menjadi gila, bagaimanapun, itulah jenis asumsi yang akan memungkinkan aktor jahat untuk bekerja. Seseorang berkata "oh, kita tahu kelas tidak akan pernah mulai dengan huruf kecil / paket tidak akan pernah dimulai dengan huruf kapital". Memang, aktor jahat yang memiliki akses ke pemuat kelas Anda sudah dapat melakukan hal-hal buruk, jadi itu mungkin bukan asumsi yang benar-benar mengerikan.
corsiKa
2
@PieterDeBie Bagaimana bisa begitu? Yang perlu Anda ketahui adalah nama metode yang ingin Anda uji.
fool4jesus
20
Java 8 menambahkan getTypeName () juga ... ingin memperbarui untuk itu?
Theodore Murdock
90

Menambahkan kelas lokal, lambdas dan toString()metode untuk melengkapi dua jawaban sebelumnya. Lebih lanjut, saya menambahkan array lambdas dan array kelas anonim (yang tidak masuk akal dalam praktiknya):

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

Ini adalah hasil lengkapnya:

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;

Jadi, inilah aturannya. Pertama, mari kita mulai dengan tipe primitif dan void:

  1. Jika objek kelas mewakili tipe primitif atau void, keempat metode hanya mengembalikan namanya.

Sekarang aturan untuk getName() metode ini:

  1. Setiap kelas atau antarmuka non-lambda dan non-array (yaitu, tingkat atas, bersarang, dalam, lokal dan anonim) memiliki nama (yang dikembalikan oleh getName() ) yaitu nama paket diikuti dengan titik (jika ada paket ), diikuti dengan nama file kelasnya seperti yang dihasilkan oleh kompiler (tanpa akhiran .class). Jika tidak ada paket, itu hanya nama file kelas. Jika kelas adalah kelas dalam, bersarang, lokal atau anonim, kompiler harus menghasilkan setidaknya satu $dalam nama file kelasnya. Perhatikan bahwa untuk kelas anonim, nama kelas akan diakhiri dengan tanda dolar diikuti dengan angka.
  2. Nama-nama kelas Lambda umumnya tidak dapat diprediksi, dan Anda seharusnya tidak peduli dengan mereka. Tepatnya, nama mereka adalah nama kelas yang dilampirkan, diikuti oleh $$Lambda$, diikuti oleh angka, diikuti oleh garis miring, diikuti oleh nomor lain.
  3. Deskripsi kelas primitif adalah Zuntuk boolean, Buntuk byte, Suntuk short, Cuntuk char, Iuntuk int, Juntuk long, Funtuk floatdan Duntuk double. Untuk kelas dan antarmuka non-array deskriptor kelas Ldiikuti oleh apa yang diberikan oleh getName()diikuti oleh ;. Untuk kelas array, deskriptor kelas [diikuti oleh deskriptor kelas dari tipe komponen (yang mungkin merupakan kelas array lain).
  4. Untuk kelas array, the getName() metode mengembalikan deskriptor kelasnya. Aturan ini tampaknya gagal hanya untuk kelas array yang tipe komponennya adalah lambda (yang mungkin merupakan bug), tetapi mudah-mudahan ini seharusnya tidak menjadi masalah juga karena tidak ada titik bahkan pada keberadaan kelas array yang tipe komponennya adalah lambda.

Sekarang, toString()metodenya:

  1. Jika instance kelas mewakili antarmuka (atau anotasi, yang merupakan jenis antarmuka khusus), toString()hasilnya kembali "interface " + getName(). Jika itu primitif, ia akan kembali dengan sederhana getName(). Jika itu sesuatu yang lain (tipe kelas, bahkan jika itu cukup aneh), ia kembali "class " + getName().

The getCanonicalName()Metode:

  1. Untuk kelas dan antarmuka tingkat atas, getCanonicalName()metode mengembalikan apa yang getName()dikembalikan metode.
  2. Itu getCanonicalName() kembali metode nulluntuk anonim atau kelas lokal dan untuk kelas array tersebut.
  3. Untuk kelas dan antarmuka bagian dalam dan bersarang, getCanonicalName()metode mengembalikan apagetName() metode yang akan menggantikan tanda dolar yang diperkenalkan oleh kompiler oleh titik.
  4. Untuk kelas array, getCanonicalName()metode ini kembali nulljika nama kanonik tipe komponennya null. Jika tidak, itu mengembalikan nama kanonik dari tipe komponen diikuti oleh [].

The getSimpleName()Metode:

  1. Untuk kelas tingkat atas, bersarang, dalam dan lokal, the getSimpleName() mengembalikan nama kelas seperti yang tertulis dalam file sumber.
  2. Untuk kelas anonim getSimpleName()mengembalikan sebuah kosongString .
  3. Untuk kelas lambda, getSimpleName()hanya mengembalikan apa yang getName()akan kembali tanpa nama paket. Ini tidak masuk akal dan terlihat seperti bug bagi saya, tetapi tidak ada gunanya memanggil getSimpleName()kelas lambda untuk memulai.
  4. Untuk kelas array getSimpleName()metode mengembalikan nama kelas komponen sederhana diikuti oleh []. Ini memiliki efek samping yang lucu / aneh yang dimiliki oleh kelas array yang jenis komponennya adalah kelas anonim [].
Victor Stafusa
sumber
2
… replacing the dollar-signs by dots: Hanya tanda-tanda dolar yang diperkenalkan sebagai pembatas yang diganti. Anda dapat memiliki dolar sebagai bagian dari nama yang sederhana, dan itu akan tetap berlaku.
MvG
Oh tidak! Sebagai bagian dari nama kelas! Saya sedang mengembangkan transformator kelas dan saya pikir '/' akan menjadi pembatas yang aman antara kelas dan nama paket: /
José Roberto Araújo Júnior
81

Selain pengamatan Nick Holt, saya menjalankan beberapa kasus untuk Arraytipe data:

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

Cetakan snipet kode di atas:

[I
int[]
int[]

[Ljava.lang.Integer;
java.lang.Integer[]
Integer[]
gerardw
sumber
28
Saya tidak akan jauh lebih baik untuk mengusulkan edit pada jawaban di atas.
LoKi
17

Saya bingung dengan berbagai skema penamaan yang berbeda, dan baru saja akan bertanya dan menjawab pertanyaan saya sendiri ketika saya menemukan pertanyaan ini di sini. Saya pikir temuan saya cukup cocok, dan melengkapi apa yang sudah ada di sini. Fokus saya mencari dokumentasi tentang berbagai istilah, dan menambahkan beberapa istilah terkait lainnya yang mungkin muncul di tempat lain.

Perhatikan contoh berikut:

package a.b;
class C {
  static class D extends C {
  }
  D d;
  D[] ds;
}
  • The nama yang sederhana dari Dadalah D. Itu hanya bagian yang Anda tulis ketika mendeklarasikan kelas. Kelas anonim tidak memiliki nama sederhana. Class.getSimpleName()mengembalikan nama ini atau string kosong. Dimungkinkan untuk nama sederhana untuk memuat $jika Anda menulisnya seperti ini, karena $ini adalah bagian yang valid dari pengidentifikasi sesuai JLS bagian 3.8 (bahkan jika agak tidak disarankan).

  • Menurut bagian JLS 6,7 , baik a.b.C.Ddan a.b.C.D.D.Dakan menjadi nama yang memenuhi syarat , tetapi hanya a.b.C.Dakan menjadi nama kanonik dari D. Jadi setiap nama kanonik adalah nama yang sepenuhnya memenuhi syarat, tetapi yang sebaliknya tidak selalu benar. Class.getCanonicalName()akan mengembalikan nama kanonik atau null.

  • Class.getName()didokumentasikan untuk mengembalikan nama biner , sebagaimana ditentukan dalam bagian JLS 13.1 . Dalam hal ini ia mengembalikan a.b.C$Duntuk Ddan [La.b.C$D;untuk D[].

  • Jawaban ini menunjukkan bahwa dimungkinkan bagi dua kelas yang dimuat oleh loader kelas yang sama untuk memiliki nama kanonik yang sama tetapi nama biner yang berbeda . Tidak ada nama yang cukup untuk menyimpulkan yang lain: jika Anda memiliki nama kanonik, Anda tidak tahu bagian mana dari nama itu adalah paket dan yang berisi kelas. Jika Anda memiliki nama biner, Anda tidak tahu mana $yang diperkenalkan sebagai pemisah dan yang merupakan bagian dari beberapa nama sederhana. (File kelas menyimpan nama biner dari kelas itu sendiri dan kelas terlampirnya , yang memungkinkan runtime untuk membuat perbedaan ini .)

  • Kelas anonim dan kelas lokal tidak memiliki nama yang sepenuhnya memenuhi syarat tetapi masih memiliki nama biner . Hal yang sama berlaku untuk kelas yang bersarang di dalam kelas tersebut. Setiap kelas memiliki nama biner.

  • Berjalan javap -v -privatepada a/b/C.classmenunjukkan bahwa bytecode merujuk pada tipe das La/b/C$D;dan array dssebagai [La/b/C$D;. Ini disebut deskriptor , dan mereka ditentukan dalam bagian 4.3 JVMS .

  • Nama kelas yang a/b/C$Ddigunakan dalam kedua deskriptor ini adalah apa yang Anda dapatkan dengan mengganti .dengan /nama biner. Spesifikasi JVM rupanya menyebut ini bentuk internal dari nama biner . JVMS bagian 4.2.1 menjelaskannya, dan menyatakan bahwa perbedaan dari nama biner adalah karena alasan historis.

  • Nama file kelas di salah satu loader kelas berbasis nama file adalah apa yang Anda dapatkan jika Anda menginterpretasikannya /dalam bentuk internal nama biner sebagai pemisah direktori, dan menambahkan ekstensi nama file .classke dalamnya. Ini diselesaikan relatif terhadap jalur kelas yang digunakan oleh pemuat kelas yang dimaksud.

MvG
sumber
3
Ini harus menjadi jawaban yang diterima karena itu satu-satunya jawaban yang merujuk JLS dan menggunakan terminologi yang tepat.
John
10

ini adalah dokumen terbaik yang saya temukan menggambarkan 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
sumber
3

Sangat menarik untuk dicatat getCanonicalName()dan getSimpleName()dapat meningkatkanInternalError ketika nama kelas salah. Ini terjadi untuk beberapa bahasa JVM non-Jawa, misalnya, Scala.

Pertimbangkan yang berikut ini (Scala 2.11 di 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

Ini bisa menjadi masalah untuk lingkungan bahasa campuran atau lingkungan yang secara dinamis memuat bytecode, misalnya, server aplikasi dan perangkat lunak platform lainnya.

Sim
sumber
2

getName () - mengembalikan nama entitas (kelas, antarmuka, kelas array, tipe primitif, atau batal) yang diwakili oleh objek Kelas ini, sebagai String.

getCanonicalName () - mengembalikan nama kanonik dari kelas dasar seperti yang didefinisikan oleh Spesifikasi Bahasa Jawa.

getSimpleName () - mengembalikan nama sederhana dari kelas yang mendasarinya, yaitu nama yang telah diberikan dalam kode sumber.

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

Satu perbedaan adalah bahwa jika Anda menggunakan kelas anonim Anda bisa mendapatkan nilai nol ketika mencoba untuk mendapatkan nama kelas menggunakangetCanonicalName()

Fakta lain adalah bahwa getName()metode berperilaku berbeda dari getCanonicalName()metode untuk kelas dalam . getName()menggunakan dolar sebagai pemisah antara nama kanonik kelas terlampir dan nama sederhana kelas dalam.

Untuk mengetahui lebih lanjut tentang mengambil nama kelas di Jawa .

Abdul Alim Shakir
sumber
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
sumber
1
Dua baris pertama di dalam metode ini dapat direduksi menjadiClass<StringBuffer> clazz = StringBuffer.class
ThePyroEagle