Чим відрізняється канонічне ім'я, просте ім'я та назва класу в Java Class?


972

У Java, яка різниця між цими:

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

Я перевіряв Javadoc кілька разів, але це ніколи не пояснює це добре. Я також провів тест, і це не відображало жодного реального значення за способом виклику цих методів.



218
Я думаю, це питання розумне. Явадок не дуже добре пояснює різницю між трьома.
Грем Борланд

1
Дивіться - docs.oracle.com/javase/6/docs/api/java/lang/Class.html або, можливо, просто напишіть тест.
Нік Холт

7
@GrahamBorland У javadoc написано "як визначено специфікацією мови Java" - тому ви можете шукати це в цьому документі. Просто тому, що це не посилання, яке можна натискати, люди все одно можуть докласти мінімальних зусиль і натиснути на перший результат пошукової системи.
vbence

66
@vbence: Більшість людей скоріше буде робити речі, ніж шукати JLS на тривіальні речі, як це. Отже, це перший результат Google :)
pathikrit

Відповіді:


1129

Якщо ви щось не впевнені, спробуйте спочатку написати тест.

Я зробив це:

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

Друкує:

int.class (примітивний):
    getName (): int
    getCanonicalName (): int
    getSimpleName (): int
    getTypeName (): int

String.class (звичайний клас):
    getName (): java.lang.String
    getCanonicalName (): java.lang.String
    getSimpleName (): Рядок
    getTypeName (): java.lang.String

java.util.HashMap.SimpleEntry.class (вкладений клас):
    getName (): java.util.Ab AbstractMap $ SimpleEntry
    getCanonicalName (): java.util.Ab AbstractMap.SimpleEntry
    getSimpleName (): SimpleEntry
    getTypeName (): java.util.Ab AbstractMap $ SimpleEntry

новий java.io.Serializable () {}. getClass () (анонімний внутрішній клас):
    getName (): ClassNameTest $ 1
    getCanonicalName (): null
    getSimpleName ():    
    getTypeName (): ClassNameTest $ 1

В останньому блоці порожній запис, де getSimpleNameповертається порожній рядок.

Результат, що дивиться на це:

  • ім'я це ім'я , яке ви будете використовувати , щоб динамічно завантажувати клас з, наприклад, виклик Class.forNameз за замовчуванням ClassLoader. У межах певного ClassLoader, всі класи мають унікальні назви.
  • канонічне ім'я це ім'я , яке буде використовуватися в операторі імпорту. Це може бути корисно під час toStringоперацій або ведення журналу. Коли javacкомпілятор має повне уявлення про класний шлях, він застосовує унікальність канонічних імен всередині нього, стискаючи повністю кваліфіковані імена класів і пакетів під час компіляції. Однак JVM повинні приймати такі зіткнення імен, і, отже, канонічні назви не ідентифікують однозначно класи в межах ClassLoader. (Заздалегідь, кращою назвою цього геттера було б getJavaName; але цей метод датується тим часом, коли JVM використовувався виключно для запуску програм Java.)
  • назва просто вільно ідентифікує клас, знову може виявитися корисним при toStringабо лісозаготівельних операцій , але не гарантовано бути унікальним.
  • то ім'я типу повертає «інформативна рядок для назви цього типу», «Це походить ToString (): це чисто інформативне і не має ніякого значення контракту» (як написано sir4ur0n)

5
Який додатковий ви вважаєте потрібним?
Нік Холт

2
@AnupamSaini так. Мати таку назву пакета в реальній програмі було б шалено.
Jayen

3
ІТ було б божевільним, проте саме таке припущення дозволило б зловмисному актору працювати. Хтось каже: "о, добре, що ми знаємо, що заняття ніколи не почнуться з малих регістрів / пакунків, і ніколи не почнуться з великих літер". Звичайно, злісний актор, який має доступ до вантажника, вже може робити жахливі речі, тому, мабуть, це не зовсім страшне припущення.
corsiKa

2
@PieterDeBie Як це? Все, що вам потрібно знати, - це ім'я методу, який ви хочете перевірити.
crazy4jesus

20
Java 8 додав getTypeName (), а також ... піклуватися про оновлення для цього?
Теодор Мердок

90

Додавання місцевих класів, лямбда та toString()метод завершення попередніх двох відповідей. Далі я додаю масиви лямбда та масиви анонімних класів (які на практиці не мають жодного сенсу):

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

Це повний вихід:

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;

Отже, ось правила. Спочатку давайте розпочнемо з примітивних типів void:

  1. Якщо об’єкт класу представляє примітивний тип або void, всі чотири методи просто повертають його ім'я.

Тепер правила getName()методу:

  1. Кожен клас чи інтерфейс без лямбда та не масив (наприклад, верхній рівень, вкладений, внутрішній, локальний та анонімний) має ім’я (яке повертається getName()), яке є ім'ям пакета, а за ним крапкою (якщо є пакет ), а потім ім'я його класового файлу, створеного компілятором (без суфіксу .class). Якщо пакету немає, це просто ім'я файлу класу. Якщо клас - це внутрішній, вкладений, локальний або анонімний клас, компілятор повинен генерувати хоча б один $у своєму імені файлу класу. Зауважте, що для анонімних класів назва класу закінчується знаком долара, а за ним цифрою.
  2. Назви класів лямбда, як правило, непередбачувані, і вам все одно не варто піклуватися про них. Точно їх ім'я - це ім'я класу, що додається $$Lambda$, за ним - число, за яким - косою рисою, а потім - інше число.
  3. Клас дескриптора примітивів Zдля boolean, Bдля byte, Sдля short, Cдля char, Iдля int, Jдля long, Fдля floatі Dдля double. Для класів та інтерфейсів без масиву дескриптору класів Lслідує те, що задано за getName()ним ;. Для класів масиву дескриптору класів слід дескриптор [класу типу компонента (який може бути самим іншим класом масиву).
  4. Для класів масиву getName()метод повертає його дескриптор класів. Це правило, здається, не відповідає лише класам масивів, тип компонента яких є лямбда (що, можливо, є помилкою), але, сподіваємось, це все одно не має значення, оскільки немає сенсу навіть у існуванні класів масивів, тип компонента яких є лямбда.

Тепер toString()спосіб:

  1. Якщо екземпляр класу представляє інтерфейс (або анотацію, яка є спеціальним типом інтерфейсу), toString()повертається "interface " + getName(). Якщо це примітив, він повертається просто getName(). Якщо це щось інше (тип класу, навіть якщо він досить дивний), він повертається "class " + getName().

getCanonicalName()метод:

  1. Для класів та інтерфейсів верхнього рівня getCanonicalName()метод повертає саме те, що getName()метод повертає.
  2. У getCanonicalName()методі повертає nullдля анонімних або локальних класів і для класів масиву з них.
  3. Для внутрішніх та вкладених класів та інтерфейсів getCanonicalName()метод повертає те, що getName()метод замінить введені компілятором знаки долара крапками.
  4. Для класів масивів getCanonicalName()метод повертається, nullякщо канонічне ім'я типу компонента null. В іншому випадку він повертає канонічну назву типу компонента, за якою слідує [].

getSimpleName()метод:

  1. Для верхнього рівня, вкладених, внутрішніх та локальних класів getSimpleName()повертає ім'я класу, як записано у вихідному файлі.
  2. Для анонімних класів getSimpleName()повертається порожнім String.
  3. Для класів лямбда getSimpleName()справедливий повертає те, що getName()повернеться без назви пакета. Це не має особливого сенсу і схоже на помилку для мене, але немає сенсу закликати getSimpleName()клас лямбда для початку.
  4. Для класів масиву getSimpleName()метод повертає просте ім'я класу компонентів, за яким слідує []. У цьому є кумедний / дивний побічний ефект, що класи масивів, тип компонента яких є анонімним класом, мають []такі ж прості назви.

2
… replacing the dollar-signs by dots: Заміняються лише знаки долара, які були введені як роздільники. Ви цілком можете мати долари як частину простого імені, і вони залишаться на місці.
MvG

О ні! Як частина назви класу! Я розробляю класний трансформатор, і я думав, що '/' буде безпечним роздільником між класом та назвою пакету: /
Хосе Роберто Арауо Жуніор

81

Окрім спостережень Ніка Хольта, я запустив кілька випадків щодо 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());

Вище фрагменти коду:

[I
int[]
int[]

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

28
Мені було б набагато краще запропонувати редагувати вищевказану відповідь.
LoKi

17

Мене також бентежить широке коло різних схем іменування, і я ось-ось збирався задати і відповісти на моє власне запитання з цього приводу, коли я знайшов це питання тут. Я думаю, що мої висновки підходять досить добре і доповнюють те, що вже є. Моя увага зосереджується на пошуку документації на різні терміни та додаванні ще декількох пов’язаних термінів, які можуть з’явитися в інших місцях.

Розглянемо наступний приклад:

package a.b;
class C {
  static class D extends C {
  }
  D d;
  D[] ds;
}
  • Просте ім'я в Dце D. Це лише та частина, яку ви написали при оголошенні класу. Анонімні класи не мають простої назви. Class.getSimpleName()повертає це ім'я або порожній рядок. Можливо, що просте ім'я містить а, $якщо ви пишете його так, оскільки $це дійсна частина ідентифікатора, згідно з розділом 3.8 JLS (навіть якщо це дещо перешкоджає).

  • Згідно розділу JLS 6.7 , як a.b.C.Dі a.b.C.D.D.Dбуло б повністю кваліфіковані імена , але тільки a.b.C.Dб бути канонічним ім'ям з D. Отже, кожне канонічне ім'я є цілком кваліфікованим ім'ям, але зворотне не завжди відповідає дійсності. Class.getCanonicalName()поверне канонічну назву або null.

  • Class.getName()документально підтверджено для повернення двійкового імені , як зазначено в розділі 13.1 JLS . У цьому випадку він повертається a.b.C$Dза Dі [La.b.C$D;для D[].

  • Ця відповідь демонструє, що для двох класів, завантажених одним завантажувачем класів, можливо однакове канонічне ім'я, але виразні двійкові імена . Жодного імені недостатньо для надійного виведення іншого: якщо у вас є канонічне ім'я, ви не знаєте, які частини імені є пакетами, а які містять класи. Якщо у вас є двійкове ім'я, ви не знаєте, які $були введені як роздільники і які були частиною простого імені. (У файлі класу зберігається двійкове ім'я самого класу та його клас , що додає , що дозволяє виконувати цей розріз .)

  • Анонімні класи та місцеві класи не мають повністю кваліфікованих імен, але все ще мають бінарне ім'я . Те саме стосується класів, вкладених у такі класи. Кожен клас має двійкове ім’я.

  • Запуск javap -v -privateна a/b/C.classпоказує , що байт - код відноситься до типу , dяк La/b/C$D;і в масиві dsяк [La/b/C$D;. Вони називаються дескрипторами , і вони визначені у розділі 4.3 JVMS .

  • Ім'я класу a/b/C$Dвикористовується в обох цих дескрипторів, що ви отримаєте, замінивши .на /в довічним імені. Специфікація JVM, очевидно, називає це внутрішньою формою бінарного імені . Розділ 4.2.1 JVMS описує це та констатує, що різниця від двійкової назви була з історичних причин.

  • Ім'я файлу класу в одному з типових завантажувачів класів на основі імені файлів - це те, що ви отримуєте, якщо інтерпретувати /внутрішню форму двійкового імені як роздільник каталогів і додавати .classдо нього розширення імені файлу . Він вирішений відносно шляху до класу, який використовується відповідним завантажувачем класів.


3
Це має бути прийнятою відповіддю, оскільки це єдина відповідь, яка посилається на JLS та використовує належні термінології.
Джон

10

це найкращий документ, який я знайшов, описуючи 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

Цікаво відзначити , що getCanonicalName()і getSimpleName()може підняти , InternalErrorколи ім'я класу мають неправильний формат. Це відбувається для деяких мов JVM, що не є Java, наприклад, Scala.

Розглянемо наступне (Scala 2.11 на 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

Це може бути проблемою для змішаних мовних середовищ або середовищ, які динамічно завантажують байт-код, наприклад, сервери додатків та інше програмне забезпечення платформи.


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
Перші два рядки всередині методу можна звести доClass<StringBuffer> clazz = StringBuffer.class
ThePyroEagle

1

getName () - повертає ім'я сутності (клас, інтерфейс, клас масиву, примітивний тип або void), представлене цим об'єктом Class, як String.

getCanonicalName () - повертає канонічну назву базового класу, як визначено специфікацією мови Java.

getSimpleName () - повертає просте ім'я базового класу, тобто ім'я, яке воно було вказане у вихідному коді.

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

Одна відмінність полягає в тому, що якщо ви використовуєте анонімний клас, ви можете отримати нульове значення при спробі отримати ім'я класу за допомогоюgetCanonicalName()

Інший факт полягає в тому, що getName()метод поводиться інакше, ніж getCanonicalName()метод для внутрішніх класів . getName()використовує долар як роздільник між канонічним ім'ям класу, що додається, та простим іменем внутрішнього класу.

Щоб дізнатися більше про отримання імені класу на Java .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.