В Java у каждого класса есть имя. Классы находятся в пакетах, и это позволяет нам программистам работать вместе, избегая конфликтов имен. Я могу назвать свой класс A
а вы также можете назвать свой класс A
если они находятся в разных пакетах и отлично работают вместе.
Если вы посмотрели на API класса Class
то наверняка заметили, что есть три разных метода, которые дают вам имя класса:
-
getSimpleName()
дает вам имя класса без пакета. -
getName()
дает вам имя класса с полным именем пакета впереди. -
getCanonicalName()
дает вам каноническое имя класса.
Все просто? Ну, первое простое, а второе также имеет смысл, если только нет этого тревожного канонического имени. Это не очевидно, что это такое. И если вы не знаете, что такое каноническое имя, вы можете почувствовать некоторое беспокойство в силу своих навыков Java и для второго. Какая разница между двумя?
Если вы хотите получить точное объяснение, обратитесь к главе 6.7 Спецификации языка Java . Здесь мы идем с чем-то более простым, нацеленным на более простое для понимания, хотя и не таким тщательным.
Давайте посмотрим несколько примеров:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
package pakage.subpackage.evensubberpackage; import org.junit.Assert; import org.junit.Test; public class WhatIsMyName { @Test public void classHasName() { final Class<?> klass = WhatIsMyName. class ; final String simpleNameExpected = "WhatIsMyName" ; Assert.assertEquals(simpleNameExpected, klass.getSimpleName()); final String nameExpected = "pakage.subpackage.evensubberpackage.WhatIsMyName" ; Assert.assertEquals(nameExpected, klass.getName()); Assert.assertEquals(nameExpected, klass.getCanonicalName()); } ... |
Этот «модульный тест» просто отлично работает. Но, как вы можете видеть, в этом случае нет разницы между именем и каноническим именем. (Обратите внимание, что название пакета — pakage
а не package
. Чтобы проверить свои навыки лексического Java, ответьте на вопрос, почему?)
Давайте посмотрим на следующий пример из того же тестового файла junit:
01
02
03
04
05
06
07
08
09
10
|
@Test public void arrayHasName() { final Class<?> klass = WhatIsMyName[]. class ; final String simpleNameExpected = "WhatIsMyName[]" ; Assert.assertEquals(simpleNameExpected, klass.getSimpleName()); final String nameExpected = "[Lpakage.subpackage.evensubberpackage.WhatIsMyName;" ; Assert.assertEquals(nameExpected, klass.getName()); final String canonicalNameExpected = "pakage.subpackage.evensubberpackage.WhatIsMyName[]" ; Assert.assertEquals(canonicalNameExpected, klass.getCanonicalName()); } |
Теперь есть различия. Когда мы говорим о массивах, простое имя сигнализирует о добавлении открывающих и закрывающих скобок, как мы это делали в исходном коде Java. «Нормальное» имя выглядит немного странно. Он начинается с L
и добавляет точку с запятой. Это отражает внутреннее представление имен классов в JVM. Каноническое имя изменилось так же, как и простое имя: оно совпадает с предыдущим для класса, в котором все имена пакетов имеют префикс с добавленными скобками. Кажется, что getName()
— это больше имя JVM класса, а getCanonicalName()
больше похоже на полное имя на уровне исходного кода Java.
Давайте продолжим с другим примером (мы все еще в том же файле):
01
02
03
04
05
06
07
08
09
10
11
12
|
class NestedClass{} @Test public void nestedClassHasName() { final Class<?> klass = NestedClass. class ; final String simpleNameExpected = "NestedClass" ; Assert.assertEquals(simpleNameExpected, klass.getSimpleName()); final String nameExpected = "pakage.subpackage.evensubberpackage.WhatIsMyName$NestedClass" ; Assert.assertEquals(nameExpected, klass.getName()); final String canonicalNameExpected = "pakage.subpackage.evensubberpackage.WhatIsMyName.NestedClass" ; Assert.assertEquals(canonicalNameExpected, klass.getCanonicalName()); } |
Разница заключается в знаке доллара в названии класса. И снова «имя» — это больше того, что используется JVM, а каноническое имя — это то, на что похож исходный код Java. Если вы скомпилируете этот код, компилятор Java сгенерирует файлы:
-
WhatIsMyName.class
и -
WhatIsMyName$NestedClass.class
Несмотря на то, что класс называется вложенным, он на самом деле является внутренним классом. Однако в именовании нет никакой разницы: статический или нестатический класс внутри другого класса просто назван одинаково. Теперь давайте посмотрим на что-то еще более интересное:
01
02
03
04
05
06
07
08
09
10
11
|
@Test public void methodClassHasName() { class MethodClass{}; final Class<?> klass = MethodClass. class ; final String simpleNameExpected = "MethodClass" ; Assert.assertEquals(simpleNameExpected, klass.getSimpleName()); final String nameExpected = "pakage.subpackage.evensubberpackage.WhatIsMyName$1MethodClass" ; Assert.assertEquals(nameExpected, klass.getName()); final String canonicalNameExpected = null ; Assert.assertEquals(canonicalNameExpected, klass.getCanonicalName()); } |
На этот раз у нас есть класс внутри метода. Не обычный сценарий, но действительный с точки зрения языка Java. Простое имя класса — это просто: простое имя класса. Не так много сюрпризов.
Однако «нормальное» имя интересно. Компилятор Java генерирует имя JVM для класса, и это имя содержит число в нем. Почему? Потому что ничто не помешает мне иметь класс с тем же именем в другом методе нашего тестового класса, и вставка числа — это способ предотвратить конфликт имен для JVM. JVM не знает и не заботится о внутренних и вложенных классах или классах, определенных внутри метода. Класс — это просто класс. Если вы скомпилируете код, вы, вероятно, увидите файл WhatIsMyName$1MethodClass.class
сгенерированный javac. Мне пришлось добавить «вероятно» не потому, что я считаю вероятность того, что вы слепы, а потому, что это имя на самом деле является внутренним делом компилятора Java. Он может выбрать другую стратегию предотвращения конфликтов имен, хотя я не знаю ни одного компилятора, который отличался бы от вышеупомянутого.
Каноническое название самое интересное. Не существует! Это ноль. Почему? Потому что вы не можете получить доступ к этому классу извне метода, определяющего его. У него нет канонического имени. Продолжим.
Как насчет анонимных классов. У них не должно быть имени. Ведь именно поэтому их называют анонимными.
01
02
03
04
05
06
07
08
09
10
|
@Test public void anonymousClassHasName() { final Class<?> klass = new Object(){}.getClass(); final String simpleNameExpected = "" ; Assert.assertEquals(simpleNameExpected, klass.getSimpleName()); final String nameExpected = "pakage.subpackage.evensubberpackage.WhatIsMyName$1" ; Assert.assertEquals(nameExpected, klass.getName()); final String canonicalNameExpected = null ; Assert.assertEquals(canonicalNameExpected, klass.getCanonicalName()); } |
На самом деле у них нет простого имени. Простое имя — пустая строка. У них есть имя, составленное компилятором. У бедного javac нет другого выбора. Это должно придумать имя даже для безымянных классов. Он должен сгенерировать код для JVM и сохранить его в некотором файле. Каноническое имя опять ноль.
Готовы ли мы с примерами? У нас есть что-то простое (ака примитивное) в конце. Java примитивы.
1
2
3
4
5
6
7
8
|
@Test public void intClassHasName() { final Class<?> klass = int . class ; final String intNameExpected = "int" ; Assert.assertEquals(intNameExpected, klass.getSimpleName()); Assert.assertEquals(intNameExpected, klass.getName()); Assert.assertEquals(intNameExpected, klass.getCanonicalName()); } |
Если класс представляет примитив, такой как int
(что может быть проще, чем int?), То простое имя, имя «the» и канонические имена — это все int
имени примитива.
Точно так же массив примитивов очень прост, не так ли?
01
02
03
04
05
06
07
08
09
10
|
@Test public void intArrayClassHasName() { final Class<?> klass = int []. class ; final String simpleNameExpected = "int[]" ; Assert.assertEquals(simpleNameExpected, klass.getSimpleName()); final String nameExpected = "[I" ; Assert.assertEquals(nameExpected, klass.getName()); final String canonicalNameExpected = "int[]" ; Assert.assertEquals(canonicalNameExpected, klass.getCanonicalName()); } |
Ну, это не просто. Имя — [I
, что немного загадочно, если вы не читаете соответствующую главу спецификации JVM. Возможно, я расскажу об этом в другой раз.
Вывод
Простое имя класса просто. «Имя», возвращаемое getName()
является интересным для вещей уровня JVM. getCanonicalName()
больше всего похож на исходный код Java.
- Вы можете получить полный исходный код приведенного выше примера из gist e789d700d3c9abc6afa0 из GitHub.
Ссылка: | Название класса от нашего партнера JCG Питера Верхаса из блога Java Deep . |