В чем разница между каноническим именем, простым именем и именем класса в Java Class?

В Java, в чем разница между ними:

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

Я проверял Javadoc несколько раз, и все же это никогда не объясняет это хорошо. Я также провел тест, и это не отражало никакого реального смысла в том, как эти методы вызываются.

Ответ 1

Если вы в чем-то не уверены, попробуйте сначала написать тест.

Я сделал это:

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 (primitive):
    getName():          int
    getCanonicalName(): int
    getSimpleName():    int
    getTypeName():      int

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

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

new java.io.Serializable(){}.getClass() (anonymous inner class):
    getName():          ClassNameTest$1
    getCanonicalName(): null
    getSimpleName():    
    getTypeName():      ClassNameTest$1

В последнем блоке есть пустая запись, где getSimpleName возвращает пустую строку.

Результат выглядит так:

  • имя - это имя, которое вы используете для динамической загрузки класса, например, при вызове Class.forName со значением по умолчанию ClassLoader. В рамках определенного ClassLoader все классы имеют уникальные имена.
  • каноническое имя - это имя, которое будет использоваться в операторе импорта. Это может быть полезно во время toString или операций регистрации. Когда компилятор javac имеет полное представление о пути к классам, он обеспечивает уникальность канонических имен внутри него, конфликтуя полностью определенные имена классов и пакетов во время компиляции. Однако JVM должны принимать такие конфликты имен, и, таким образом, канонические имена не уникально идентифицируют классы в пределах ClassLoader. (Оглядываясь назад, лучшим названием для этого геттера было бы getJavaName; но этот метод датируется тем временем, когда JVM использовалась исключительно для запуска программ Java.)
  • простое имя слабо идентифицирует класс, опять же может быть полезно во время toString или операций регистрации, но не гарантированно будет уникальным.
  • имя типа возвращает "информативную строку для имени этого типа", "Это похоже на toString(): оно чисто информативное и не имеет значения контракта" (как написано sir4ur0n)

Ответ 2

Добавление локальных классов, лямбда-выражений и метода 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() возвращает простое имя класса компонента, за которым следует []. У этого есть забавный/странный побочный эффект, что у классов массива, тип компонента которых является анонимным классом, есть только [] как их простые имена.

Ответ 3

В дополнение к наблюдениям Ника Холта, я провел несколько случаев для типа данных 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[]

Ответ 4

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

Рассмотрим следующий пример:

package a.b;
class C {
  static class D extends C {
  }
  D d;
  D[] ds;
}
  • Простое имя simple name для D - D. Это та часть, которую вы написали, когда объявляли класс. Анонимные классы не имеют простого имени. Class.getSimpleName() возвращает это имя или пустую строку. Простое имя может содержать $, если вы напишите его так, поскольку $ является допустимой частью идентификатора согласно разделу 3.8 JLS (даже если это несколько не рекомендуется).

  • Согласно в разделе 6.7 JLS и a.b.C.D, и a.b.C.D.D.D будут полные имена, но только a.b.C.D будет каноническим именем из D. Таким образом, каждое каноническое имя является полностью определенным именем, но не всегда верно. Class.getCanonicalName() возвращает каноническое имя или null.

  • Class.getName() задокументировано для возврата двоичного имени binary name, как указано в разделе 13.1 JLS JLS section 13.1. В этом случае он возвращает a.b.C$D для D и [La.b.C$D; для D[].

  • Этот ответ демонстрирует, что два класса, загруженные одним и тем же загрузчиком классов, могут иметь одно и то же каноническое имя canonical name, но разные двоичные имена binary names. Ни одно из имен не является достаточным для надежного вывода другого: если у вас есть каноническое имя, вы не знаете, какие части имени являются пакетами, а какие содержат классы. Если у вас есть двоичное имя, вы не знаете, какие $ были введены как разделители, а какие были частью какого-то простого имени. (Файл класса хранит двоичное имя самого класса class itself и включающего его класса enclosing class, что позволяет среде выполнения проводить это различие. )

  • Локальные классы анонимных классов и local classes не имеют полностью определенных имен fully qualified names, но все еще имеют двоичное имя binary name. То же самое относится к классам, вложенным в такие классы. Каждый класс имеет двоичное имя.

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

  • Имя класса a/b/C$D, используемое в обоих этих дескрипторах, - это то, что вы получите, заменив . на / в двоичном имени. Спецификация JVM явно называет это внутренней формой двоичного имени. Раздел JVMS 4.2.1 описывает его и утверждает, что отличие от двоичного имени было по историческим причинам.

  • Имя файла file name класса в одном из типичных загрузчиков классов на основе имени файла - это то, что вы получите, если интерпретировать / во внутренней форме двоичного имени как разделитель каталогов и добавить файл расширение имени .class к нему. Он разрешен относительно пути к классу, используемого рассматриваемым загрузчиком классов.

Ответ 5

это лучший документ, который я нашел, описывая 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[]

Ответ 6

Интересно отметить, что getCanonicalName() и getSimpleName() могут поднять InternalError, когда имя класса искажено. Это происходит для некоторых языков Java, отличных от 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

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

Ответ 7

    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

Ответ 8

getName() - возвращает имя объекта (класс, интерфейс, класс массива, тип примитива или void), представленного этим объектом Class, в виде строки.

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.