Статьи

Наименование класса

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