В чем разница между каноническим именем, простым именем и именем класса в Java?
Заголовок: В чем разница между getSimpleName(), getName() и getCanonicalName() в Java?
Тело вопроса:
В Java я столкнулся с некоторой неясностью относительно методов получения имени класса. Рассматриваю следующий код:
Object o1 = ....;
o1.getClass().getSimpleName();
o1.getClass().getName();
o1.getClass().getCanonicalName();
Я проверял документацию Javadoc несколько раз, и ни разу она не объясняла эту разницу должным образом. Я также провел тесты, но они не показали какого-либо значимого различия в поведении этих методов.
Не могли бы вы объяснить разницу между getSimpleName(), getName() и getCanonicalName()? Когда и в каких случаях следует использовать каждый из них?
5 ответ(ов)
Если вы не уверены в чем-то, попробуйте сначала писать тест.
Я сделал следующее:
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:
- Вот Спецификация Java 11 по данному вопросу: https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.7
Пример 6.7-2. и Пример 6.7-3. рассматривает Полностью квалифицированные имена и Полностью квалифицированные имена против канонического имени соответственно.
Для ответа на вопрос на 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()
- Для обычных классов и интерфейсов (топ-уровень, вложенные, внутренние, локальные и анонимные) метод возвращает имя, состоящее из имени пакета, точки (если пакет существует) и имени класса. Для вложенных и анонимных классов в имени будет как минимум один символ
$. - Имена лямбда-классов непредсказуемы и выглядят как имя класса, за которым следует
$$Lambda$, номер, затем слэш и ещё один номер. - Дескрипторы классов для примитивов:
Z(boolean),B(byte),S(short),C(char),I(int),J(long),F(float),D(double). Для не-массивных классов и интерфейсов:L+getName()+;. Для массивов:[+ дескриптор компонента (который также может быть массивом). - Для массивов метод
getName()возвращает их дескриптор. Это правило, похоже, не срабатывает для массивов, чьим компонентом является лямбда (возможно, это ошибка), но не имеет значения, так как массивы с компонентами-лямбдами, как правило, нецелесообразны.
3. Метод toString()
- Если экземпляр представляет интерфейс (или аннотацию), то
toString()возвращает"interface " + getName(). Для примитивов возвращается толькоgetName(). Для других классов возвращается"class " + getName().
4. Метод getCanonicalName()
- Для обычных классов и интерфейсов возвращает то же, что и
getName(). - Возвращает
nullдля анонимных и локальных классов, а также для массивов этих классов. - Для внутренних и вложенных классов заменяет символы
$на.в имени. - Для массивов возвращает
null, если каноническое имя компонента —null. В противном случае возвращается каноническое имя компонента с добавлением[].
5. Метод getSimpleName()
- Для классов топ-уровня, вложенных, внутренних и локальных возвращает имя класса из исходного файла.
- Для анонимных классов возвращает пустую строку.
- Для лямбда-классов возвращает имя из
getName()без имени пакета, что выглядит как ошибка. - Для массивов возвращает простое имя компонента с добавлением
[].
Заключение
Эти правила помогают понять, как Java обрабатывает разные виды классов и их соответственно как представление в виде строк. Если у вас есть дополнительные вопросы или нужна помощь, не стесняйтесь спрашивать!
В дополнение к наблюдениям Ника Холта, я также выполнил несколько тестов для типа данных 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 различает примитивные и объектные массивы.
На вопрос о различиях между методами getName(), getCanonicalName() и getSimpleName() класса Class в Java, я нашёл очень полезный документ. Вот краткое описание, основанное на приведённых примерах:
Примитивные типы:
int.class.getName()возвращает"int".int.class.getCanonicalName()возвращает"int".int.class.getSimpleName()возвращает"int".
Стандартные классы (например,
Integer):Integer.class.getName()возвращает"java.lang.Integer".Integer.class.getCanonicalName()возвращает"java.lang.Integer".Integer.class.getSimpleName()возвращает"Integer".
Вложенные классы (например,
Map.Entry):Map.Entry.class.getName()возвращает"java.util.Map$Entry".Map.Entry.class.getCanonicalName()возвращает"java.util.Map.Entry".Map.Entry.class.getSimpleName()возвращает"Entry".
Анонимные внутренние классы:
- Для анонимного класса, например,
new Cloneable() {},getName()выдаст что-то вроде"somepackage.SomeClass$1",getCanonicalName()вернетnull, аgetSimpleName()вернет пустую строку.
- Для анонимного класса, например,
Массивы примитивов:
- Для массива
int[]:getName()вернет"[I",getCanonicalName()вернет"int[]", аgetSimpleName()вернет"int[]".
- Для массива
Массивы объектов:
- Для массива объектов типа
Integer[]:getName()вернёт"[Ljava.lang.Integer;",getCanonicalName()вернёт"java.lang.Integer[]", аgetSimpleName()вернёт"Integer[]".
- Для массива объектов типа
Таким образом, можно заключить, что:
getName()возвращает полное имя класса, включая пакеты и символы для указания вложенности.getCanonicalName()также возвращает полное имя, но в более читабельном виде, где вложенные классы отображаются через точку.getSimpleName()возвращает только имя класса без пакета.
Для более детального изучения, можно обратиться к оригинальному документу.
Интересно отметить, что методы 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
Это может стать проблемой в средах, где смешиваются языки, или в средах, которые динамически загружают байт-код, например, в серверах приложений и другом платформенном ПО.
Инициализация ArrayList в одну строчку
Что значит 'synchronized'?
Почему нет ConcurrentHashSet, если есть ConcurrentHashMap?
Как объявить массив в одну строку?
Какие проблемы следует учитывать при переопределении equals и hashCode в Java?