11

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

14

Заголовок: В чем разница между getSimpleName(), getName() и getCanonicalName() в Java?

Тело вопроса:

В Java я столкнулся с некоторой неясностью относительно методов получения имени класса. Рассматриваю следующий код:

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

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

Не могли бы вы объяснить разницу между getSimpleName(), getName() и getCanonicalName()? Когда и в каких случаях следует использовать каждый из них?

5 ответ(ов)

13

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

Я сделал следующее:

class ClassNameTest {
    public static void main(final String... arguments) {
        printNamesForClass(
            int.class,
            "int.class (примитивный тип)");
        printNamesForClass(
            String.class,
            "String.class (обычный класс)");
        printNamesForClass(
            java.util.HashMap.SimpleEntry.class,
            "java.util.HashMap.SimpleEntry.class (вложенный класс)");
        printNamesForClass(
            new java.io.Serializable(){}.getClass(),
            "new java.io.Serializable(){}.getClass() (анониминый внутренний класс)");
    }

    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()); // добавлено в 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():    String
    getTypeName():      java.lang.String

java.util.HashMap.SimpleEntry.class (вложенный класс):
    getName():          java.util.AbstractMap$SimpleEntry
    getCanonicalName(): java.util.AbstractMap.SimpleEntry
    getSimpleName():    SimpleEntry
    getTypeName():      java.util.AbstractMap$SimpleEntry

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

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

Основные моменты, на которые стоит обратить внимание:

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

Также вы можете обратиться к документации Спецификации языка Java для получения технических подробностей API Java:

Пример 6.7-2. и Пример 6.7-3. рассматривает Полностью квалифицированные имена и Полностью квалифицированные имена против канонического имени соответственно.

1

Для ответа на вопрос на StackOverflow, вот перевод на русский языка, который описывает поведение классов в Java, включая примитивные типы, локальные и анонимные классы, лямбды и методы toString().


Давайте рассмотрим, как работают классы и их методы getName(), getCanonicalName(), getSimpleName() и toString() в Java, с особым акцентом на примитивные типы, локальные и анонимные классы, а также лямбды.

Вывод программы

Приведенный код создает экземпляры различных типов классов и выводит информацию о них. Вот полный вывод программы:

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

... (и другие типы)

Правила работы методов

1. Примитивные типы и void

Если объект класса представляет примитивный тип или void, все четыре метода просто возвращают его имя.

2. Метод getName()

  1. Для обычных классов и интерфейсов (топ-уровень, вложенные, внутренние, локальные и анонимные) метод возвращает имя, состоящее из имени пакета, точки (если пакет существует) и имени класса. Для вложенных и анонимных классов в имени будет как минимум один символ $.
  2. Имена лямбда-классов непредсказуемы и выглядят как имя класса, за которым следует $$Lambda$, номер, затем слэш и ещё один номер.
  3. Дескрипторы классов для примитивов: Z (boolean), B (byte), S (short), C (char), I (int), J (long), F (float), D (double). Для не-массивных классов и интерфейсов: L + getName() + ;. Для массивов: [ + дескриптор компонента (который также может быть массивом).
  4. Для массивов метод getName() возвращает их дескриптор. Это правило, похоже, не срабатывает для массивов, чьим компонентом является лямбда (возможно, это ошибка), но не имеет значения, так как массивы с компонентами-лямбдами, как правило, нецелесообразны.

3. Метод toString()

  1. Если экземпляр представляет интерфейс (или аннотацию), то toString() возвращает "interface " + getName(). Для примитивов возвращается только getName(). Для других классов возвращается "class " + getName().

4. Метод getCanonicalName()

  1. Для обычных классов и интерфейсов возвращает то же, что и getName().
  2. Возвращает null для анонимных и локальных классов, а также для массивов этих классов.
  3. Для внутренних и вложенных классов заменяет символы $ на . в имени.
  4. Для массивов возвращает null, если каноническое имя компонента — null. В противном случае возвращается каноническое имя компонента с добавлением [].

5. Метод getSimpleName()

  1. Для классов топ-уровня, вложенных, внутренних и локальных возвращает имя класса из исходного файла.
  2. Для анонимных классов возвращает пустую строку.
  3. Для лямбда-классов возвращает имя из getName() без имени пакета, что выглядит как ошибка.
  4. Для массивов возвращает простое имя компонента с добавлением [].

Заключение

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

0

В дополнение к наблюдениям Ника Холта, я также выполнил несколько тестов для типа данных 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();

// Массив объектов
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[]

Как видно из результатов, для примитивного массива (int[]) получаем имя класса в виде [I, что указывает на массив целых чисел. Метод getCanonicalName() возвращает int[], а getSimpleName() выводит просто int[].

Что касается массива объектов (Integer[]), то getName() возвращает [Ljava.lang.Integer;, что соответствует массиву объектов класса Integer, getCanonicalName() дает java.lang.Integer[], а getSimpleName() возвращает просто Integer[].

Таким образом, использование различных методов для получения имени класса разными способами показывает, как Java различает примитивные и объектные массивы.

0

На вопрос о различиях между методами getName(), getCanonicalName() и getSimpleName() класса Class в Java, я нашёл очень полезный документ. Вот краткое описание, основанное на приведённых примерах:

  1. Примитивные типы:

    • int.class.getName() возвращает "int".
    • int.class.getCanonicalName() возвращает "int".
    • int.class.getSimpleName() возвращает "int".
  2. Стандартные классы (например, Integer):

    • Integer.class.getName() возвращает "java.lang.Integer".
    • Integer.class.getCanonicalName() возвращает "java.lang.Integer".
    • Integer.class.getSimpleName() возвращает "Integer".
  3. Вложенные классы (например, Map.Entry):

    • Map.Entry.class.getName() возвращает "java.util.Map$Entry".
    • Map.Entry.class.getCanonicalName() возвращает "java.util.Map.Entry".
    • Map.Entry.class.getSimpleName() возвращает "Entry".
  4. Анонимные внутренние классы:

    • Для анонимного класса, например, new Cloneable() {}, getName() выдаст что-то вроде "somepackage.SomeClass$1", getCanonicalName() вернет null, а getSimpleName() вернет пустую строку.
  5. Массивы примитивов:

    • Для массива int[]: getName() вернет "[I", getCanonicalName() вернет "int[]", а getSimpleName() вернет "int[]".
  6. Массивы объектов:

    • Для массива объектов типа Integer[]: getName() вернёт "[Ljava.lang.Integer;", getCanonicalName() вернёт "java.lang.Integer[]", а getSimpleName() вернёт "Integer[]".

Таким образом, можно заключить, что:

  • getName() возвращает полное имя класса, включая пакеты и символы для указания вложенности.
  • getCanonicalName() также возвращает полное имя, но в более читабельном виде, где вложенные классы отображаются через точку.
  • getSimpleName() возвращает только имя класса без пакета.

Для более детального изучения, можно обратиться к оригинальному документу.

0

Интересно отметить, что методы 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 других записей

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 других записей

scala> c.getClass.getName
res2: String = C

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

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь