В 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 . |