В чем разница между каноническим именем, простым именем и именем класса в 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 в одну строчку
Что значит "Не удалось найти или загрузить основной класс"?
Как установить Java 8 на Mac
Почему в RecyclerView отсутствует onItemClickListener()?
Что значит 'synchronized'?