Глава 6. Имена
Содержание
- 6.1. Объявления
- 6.2. Имена и идентификаторы
- 6.3. Область видимости объявления
- 6.4. Перекрытие и затемнение
- 6.5. Определение значения имени
- 6.6. Управление доступом
- 6.7. Полные имена и канонические имена
Имена используются для ссылки на сущности, объявленные в программе.
Объявленная сущность (§6.1) — это пакет, тип класса (обычный или перечисления), тип интерфейса (обычный или тип аннотации), член (класс, интерфейс, поле или метод) типа ссылки, параметр типа (класса, интерфейса, метода или конструктора), параметр (метода, конструктора или обработчика исключений) или локальная переменная.
Имена в программах могут быть простыми, состоящими из одного идентификатора, или квалифицированными, состоящими из последовательности идентификаторов, разделённых символами "." (§6.2).
Каждое объявление, вводящее имя, имеет область видимости (§6.3), которая представляет собой часть текста программы, в пределах которой объявленная сущность может быть указана простым именем.
Квалифицированное имя N.x может использоваться для ссылки на член пакета или типа ссылки, где N — это простое или квалифицированное имя, а x — идентификатор. Если N обозначает пакет, то x является членом этого пакета, который представляет собой либо тип класса или интерфейса, либо подпакет. Если N обозначает тип ссылки или переменную типа ссылки, то x обозначает член этого типа, который представляет собой либо класс, интерфейс, поле или метод.
При определении значения имени (§6.5), для устранения неоднозначности между пакетами, типами, переменными и методами с одинаковым именем используется контекст его появления.
Управление доступом (§6.6) может быть указано в объявлении класса, интерфейса, метода или поля для контроля, когда разрешается доступ к члену. Доступ — это другое понятие по сравнению с областью видимости. Доступ определяет часть текста программы, в которой объявленная сущность может быть указана квалифицированным именем, выражением доступа к полю (§15.11) или выражением вызова метода (§15.12), в котором метод не указан простым именем. По умолчанию член можно получить из любого места в пакете, содержащем его объявление; другие возможности — public, protected и private.
Полные и канонические имена (§6.7) также рассматриваются в этой главе.
Объявление вводит сущность в программу и включает идентификатор (§3.8), который может быть использован в имени для ссылки на эту сущность.
Объявленная сущность может быть одной из следующих:
-
Пакет, объявленный в объявлении пакета (§7.4)
-
Импортированный тип, объявленный в объявлении импорта одного типа (§7.5.1) или в объявлении импорта типа по умолчанию (§7.5.2)
-
Класс, объявленный в объявлении типа класса (§8.1)
-
Интерфейс, объявленный в объявлении типа интерфейса (§9.1)
-
Переменная типа (§4.4), объявленная как параметр типа обобщенного класса (§8.1.2), интерфейса (§9.1.2), метода (§8.4.4) или конструктора (§8.8.1).
-
Член типа ссылочного типа (§8.2, §9.2, §10.7), один из следующих:
-
Параметр, один из следующих:
-
Локальная переменная, один из следующих:
Конструкторы (§8.8) также вводятся объявлениями, но используют имя класса, в котором они объявлены, а не вводят новое имя.
Библиотеки классов платформы Java SE стараются использовать, по возможности, имена, выбранные в соответствии с приведенными ниже соглашениями. Эти соглашения помогают сделать код более читаемым и избежать определенных видов конфликтов имен.
Мы рекомендуем использовать эти соглашения во всех программах, написанных на языке программирования Java. Однако не следует следовать этим соглашениям слишком строго, если долго устоявшаяся практика диктует другое. Например, методы sin и cos класса java.lang.Math имеют математически привычные имена, даже если эти имена методов нарушают предлагаемое здесь соглашение, поскольку они короткие и не являются глаголами.
Имена пакетов
Разработчики должны принять меры, чтобы избежать возможности существования двух опубликованных пакетов с одинаковым именем, выбирая уникальные имена пакетов для пакетов, которые широко распространяются. Это позволяет легко и автоматически устанавливать и каталогизировать пакеты. В этом разделе указано рекомендуемое соглашение для создания таких уникальных имен пакетов. Реализации платформы Java SE должны поддерживать автоматическое преобразование набора пакетов из локальных и обычных имен пакетов в уникальный формат имени, описанный здесь.
Если не используются уникальные имена пакетов, могут возникнуть конфликты имен пакетов, не связанных с местом создания одного из конфликтных пакетов. Это может создать ситуацию, которую трудно или невозможно решить пользователю или программисту. Класс ClassLoader может использоваться для изоляции пакетов с одинаковым именем друг от друга в тех случаях, когда пакеты будут иметь ограниченные взаимодействия, но не таким образом, чтобы это было прозрачным для простой программы.
Вы формируете уникальное имя пакета, сначала имея (или принадлежа в организации, которая имеет) имя доменного имени в интернете, например oracle.com. Затем вы инвертируете это имя, компонент за компонентом, чтобы получить, в этом примере, com.oracle, и используете его в качестве префикса для ваших имён пакетов, используя соглашение, разработанное в вашей организации для дальнейшего администрирования имен пакетов. Такое соглашение может определять, что определённые компоненты имени пакета являются названиями подразделения, отдела, проекта, машины или логина.
Пример 6.1-1. Уникальные имена пакетов
com.nighthacks.java.jag.scrabble org.openjdk.tools.compiler net.jcip.annotations edu.cmu.cs.bovik.cheese gov.whitehouse.socks.mousefinder
Первый компонент уникального имени пакета всегда записывается строчными латинскими буквами ASCII и должен быть одним из доменных имен верхнего уровня, таких как com, edu, gov, mil, net или org, или одним из двухбуквенных английских кодов, идентифицирующих страны, как указано в Стандарте ISO 3166.
Имя пакета не должно подразумевать, где пакет хранится в Интернете. Предлагаемое соглашение для создания уникальных имен пакетов просто способ привязать соглашение об именовании пакетов к существующему широко известному регистру уникальных имён, вместо того, чтобы создавать отдельный реестр для имён пакетов.
Например, пакет с именем edu.cmu.cs.bovik.cheese не обязательно можно получить по интернет-адресу cmu.edu или cs.cmu.edu или bovik.cs.cmu.edu.
В некоторых случаях доменное имя может не быть допустимым именем пакета. Вот некоторые рекомендации по работе с такими ситуациями:
-
Если доменное имя содержит дефис или любой другой специальный символ, не разрешённый в идентификаторе (§3.8), замените его на подчёркивание.
-
Если какой-либо из получившихся компонентов имени пакета является ключевым словом (§3.9), добавьте к нему подчёркивание.
-
Если какой-либо из получившихся компонентов имени пакета начинается с цифры или любого другого символа, не разрешённого в качестве начального символа идентификатора, добавьте подчёркивание перед компонентом.
Имена пакетов, предназначенные только для локального использования, должны иметь первый идентификатор, начинающийся со строчной буквы, но этот первый идентификатор не должен быть идентификатором java; имена пакетов, начинающиеся с идентификатора java, зарезервированы для пакетов платформы Java SE.
Имена типов классов и интерфейсов
Имена типов классов должны быть описательными существительными или словосочетаниями, не слишком длинными, со смешанным регистром, при этом первая буква каждого слова должна быть заглавной.
Пример 6.1-2. Описательные имена классов
ClassLoaderSecurityManagerThreadDictionary BufferedInputStream
Аналогично, имена типов интерфейсов должны быть короткими и описательными, не чрезмерно длинными, с использованием смешанного регистра, где первая буква каждого слова должна быть заглавной. Имя может быть описательным существительным или словосочетанием, что уместно, когда интерфейс используется как абстрактный суперкласс, например, интерфейсы java.io.DataInput и java.io.DataOutput; или это может быть прилагательное, описывающее поведение, как в интерфейсах Runnable и Cloneable.
Имена переменных типа
Имена переменных типа должны быть ёмкими (если возможно, одним символом) и выразительными, и не должны включать строчные буквы. Это облегчает различение параметров типа от обычных классов и интерфейсов.
Типы контейнеров должны использовать имя E для типа их элементов. Карты должны использовать K для типа их ключей и V для типа их значений. Имя X должно использоваться для произвольных типов исключений. Мы используем T для типа, когда нет более специфичных различий в типе. (Это часто встречается в методах с параметрами дженериков).
Если имеется несколько параметров типа, обозначающих произвольные типы, следует использовать буквы, соседствующие с T в алфавите, например, S. В качестве альтернативы допустимо использовать числовые индексы (например, T1, T2) для различения различных переменных типа. В таких случаях все переменные с тем же префиксом должны иметь индексы.
Если метод с дженериками появляется внутри дженерик-класса, рекомендуется избегать использования одинаковых имён для параметров типа метода и класса, чтобы избежать путаницы. То же самое относится к вложенным дженерик-классам.
Пример 6.1-3. Обычные имена переменных типа
public class HashSet<E> extends AbstractSet<E> { ... }
public class HashMap<K,V> extends AbstractMap<K,V> { ... }
public class ThreadLocal<T> { ... }
public interface Functor<T, X extends Throwable> {
T eval() throws X;
}
Когда параметры типа не подходят под одну из упомянутых категорий, имена должны быть выбраны как можно более осмысленными в рамках одного символа. Упомянутые выше имена (E, K, V, X, T) не должны использоваться для параметров типа, которые не попадают в указанные категории.
Имена методов
Имена методов должны быть глаголами или глагольными словосочетаниями, со смешанным регистром, с первой буквой в нижнем регистре и первой буквой каждого последующего слова в верхнем регистре. Ниже приведены дополнительные конкретные соглашения для имён методов:
-
Методы для получения и установки атрибута, который можно рассматривать как переменную V должны называться
getVиsetV. Примером являются методыgetPriorityиsetPriorityклассаThread. -
Метод, возвращающий длину чего-либо, должен называться
length, как в классеString. -
Метод, проверяющий булево условие V об объекте, должен называться
isV. Примером является методisInterruptedклассаThread. -
Метод, преобразующий свой объект в определённый формат F, должен называться
toF. Примерами являются методtoStringклассаObjectи методыtoLocaleStringиtoGMTStringклассаjava.util.Date.
По возможности и применимости, основание имён методов в новом классе на именах в существующем похожем классе, особенно классе из API платформы Java SE, сделает его использование проще.
Имена полей
Имена полей, которые не являются final, должны быть в смешанном регистре с первой буквой в нижнем регистре и первой буквой последующих слов в верхнем регистре. Обратите внимание, что хорошо спроектированные классы имеют очень мало public или protected полей, за исключением полей, которые являются константами (static final поля).
Поля должны иметь имена, являющиеся существительными, словосочетаниями из существительных или сокращениями существительных.
Примеры этого соглашения - поля buf, pos и count класса java.io.ByteArrayInputStream и поле bytesTransferred класса java.io.InterruptedIOException.
Имена констант
Имена констант в типах интерфейсов должны быть, а final переменные типов классов могут быть традиционно, последовательностью одного или нескольких слов, аббревиатур или сокращений, полностью заглавными, с компонентами, разделенными символами подчёркивания "_". Имена констант должны быть описательными и не излишне сокращенными. Традиционно они могут быть любой соответствующей частью речи.
Примеры имён констант включают MIN_VALUE, MAX_VALUE, MIN_RADIX и MAX_RADIX класса Character.
Группа констант, представляющих альтернативные значения набора или, реже, маскирующих битов в целочисленном значении, иногда удобно задавать с общим аббревиатурным префиксом в качестве имени.
Например:
interface ProcessStates {
int PS_RUNNING = 0;
int PS_SUSPENDED = 1;
}
Имена локальных переменных и параметров
Имена локальных переменных и параметров должны быть короткими, но осмысленными. Они часто представляют собой короткие последовательности строчных букв, которые не являются словами, например:
-
Аббревиатуры, то есть первая буква ряда слов, как в
cpдля переменной, хранящей ссылку наColoredPoint -
Сокращения, как в
buf, хранящем указатель на буфер какого-либо рода -
Мнемонические термины, организованные каким-либо образом для облегчения запоминания и понимания, обычно путём использования набора локальных переменных с традиционными именами, основанными на именах параметров широко используемых классов. Например:
-
inиout, когда вовлечены какие-либо ввод и вывод, основанные на поляхSystem -
offиlen, когда вовлечены смещение и длина, основанные на параметрах методовreadиwriteинтерфейсовDataInputиDataOutputклассаjava.io
-
Имена локальных переменных или параметров из одного символа следует избегать, за исключением временных и циклических переменных или когда переменная содержит неопределённое значение типа. Традиционные имена из одного символа:
-
bдляbyte -
cдляchar -
dдляdouble -
eдляException -
fдляfloat -
i,jиkдляint -
lдляlong -
oдляObject -
sдляString -
vдля произвольного значения некоторого типа
Имена локальных переменных или параметров, состоящих только из двух или трех строчных букв, не должны конфликтовать с начальными кодами стран и доменными именами, являющимися первой частью уникальных имён пакетов.
Имя используется для ссылки на сущность, объявленную в программе.
Существуют два вида имён: простые имена и квалифицированные имена.
Простое имя — это один идентификатор.
Квалифицированное имя состоит из имени, маркера "." и идентификатора.
При определении значения имени (§6.5) учитывается контекст, в котором это имя используется. Правила §6.5 различают контексты, где имя должно обозначать (ссылаться) на пакет (§6.5.3), тип (§6.5.5), переменную или значение в выражении (§6.5.6) или метод (§6.5.7).
У пакетов и ссылочных типов есть члены, к которым можно получить доступ с помощью квалифицированных имён. Для контекста квалифицированных имён и определения значений имён см. описания членства в §4.4, §4.5.2, §4.8, §4.9, §7.1, §8.2, §9.2 и §10.7.
Не все идентификаторы в программе являются частью имени. Идентификаторы также используются в следующих ситуациях:
-
В объявлениях (§6.1), где идентификатор может использоваться для указания имени, под которым будет известна объявленная сущность.
-
В качестве меток в меткоах выражениях (§14.7) и в
breakиcontinueвыражениях (§14.15, §14.16), которые ссылаются на метки выражений. -
В выражениях доступа к полям (§15.11), где идентификатор следует за маркером "
." для указания члена объекта, который является значением выражения, или ключевым словомsuper, которое стоит перед маркером "." -
В некоторых выражениях вызова методов (§15.12), где идентификатор может стоять после маркера "
." и перед маркером "(" для указания метода, который нужно вызвать для объекта, который является значением выражения, или ключевым словомsuper, которое стоит перед маркером "." -
В выражениях создания экземпляров квалифицированных классов (§15.9), где идентификатор стоит непосредственно справа от левого маркера
newдля указания типа, который должен быть членом типа выражения-первичного элемента, предшествующего маркером ".", предшествующему левому маркеромnew.
В этой программе:
class Test {
public static void main(String[] args) {
Class c = System.out.getClass();
System.out.println(c.toString().length() +
args[0].length() + args.length);
}
}
идентификаторы Test, main, и первые вхождения args и c не являются именами. Вместо этого они используются в объявлениях для указания имён объявленных сущностей. В примере появляются имена String, Class, System.out.getClass, System.out.println, c.toString, args и args.length. Вхождение length в args[0].length() не является именем, а идентификатором, используемым в выражении вызова метода (§15.12). Вхождение length в args.length является именем, поскольку args.length — это квалифицированное имя (§6.5.6.2), а не выражение доступа к полю (§15.11). (Выражение доступа к полю, как и выражение вызова метода, использует идентификатор, а не имя, для обозначения интересующего члена.)
Можно задаться вопросом, почему в этих выражениях используется идентификатор, а не простое имя, которое, в конце концов, тоже просто идентификатор. Причина в том, что простое имя выражения определяется в терминах лексической среды; то есть, простое имя выражения должно быть в области видимости объявления переменной. Однако доступ к полю, вызов метода, квалифицированные выражения создания экземпляров классов — все они обозначают члены, чьи имена не находятся в лексической среде. По определению такие имена связаны только в контексте, предоставляемом первичным выражением выражения доступа к полю, выражения вызова метода или выражения создания экземпляра класса. Поэтому мы обозначаем такие члены идентификаторами, а не простыми именами.
Чтобы ещё больше усложнить ситуацию, выражение доступа к полю — не единственный способ обозначения поля объекта. По причинам парсинга квалифицированное имя используется для обозначения поля переменной в области видимости. (Сама переменная обозначается простым именем, упомянутым выше.) Необходимо для контроля доступа (§6.6) захватить оба механизма для обозначения поля.
Идентификаторы, используемые в меткоах выражениях (§14.7) и связанных break и continue выражениях (§14.15, §14.16) полностью отделены от идентификаторов, используемых в объявлениях.
Пример 6.2-1. Идентификаторы и затемнение
Следующий код был взят из версии класса String и его метода indexOf, где метка изначально называлась test. Изменение метки на то же имя, что и локальной переменной i, не затемняет (§6.4.2) метку в области видимости объявления i. Таким образом, код корректен.
class Test {
char[] value;
int offset, count;
int indexOf(TestString str, int fromIndex) {
char[] v1 = value, v2 = str.value;
int max = offset + (count - str.count);
int start = offset + ((fromIndex < 0) ? 0 : fromIndex);
i:
for (int i = start; i <= max; i++) {
int n = str.count, j = i, k = str.offset;
while (n-- != 0) {
if (v1[j++] != v2[k++])
continue i;
}
return i - offset;
}
return -1;
}
}
Идентификатор max также мог бы использоваться в качестве метки выражения; метка не затемняла бы локальную переменную max внутри меткоах выражения.
Область действия объявления — это область программы, в пределах которой к сущности, объявленной этим объявлением, можно обратиться по простому имени, при условии, что она видна (§6.4.1).
Говорят, что объявление находится в области действия в конкретной точке программы, если и только если область действия объявления включает в себя эту точку.
Область действия объявления наблюдаемого (§7.4.3) пакета верхнего уровня охватывает все наблюдаемые единицы компиляции (§7.3).
Объявление пакета, который не наблюдаем, никогда не находится в области действия.
Объявление подпакета никогда не находится в области действия.
Пакет java всегда находится в области действия.
Область действия типа, импортированного с помощью объявления импорта одного типа (§7.5.1) или объявления импорта множества типов (§7.5.2), охватывает все объявления типов классов и интерфейсов (§7.6) в единице компиляции, в которой находится import объявление, а также любые аннотации в объявлении пакета (если таковые имеются) этой единицы компиляции.
Область действия члена, импортированного с помощью объявления импорта одного статического члена (§7.5.3) или объявления импорта множества статических членов (§7.5.4), охватывает все объявления типов классов и интерфейсов (§7.6) в единице компиляции, в которой находится import объявление, а также любые аннотации в объявлении пакета (если таковые имеются) этой единицы компиляции.
Область действия типа верхнего уровня (§7.6) охватывает все объявления типов в пакете, в котором объявлен тип верхнего уровня.
Область действия объявления члена m, объявленного или унаследованного типом класса C (§8.1.6), охватывает все тело C, включая любые объявления вложенных типов.
Область действия объявления члена m, объявленного или унаследованного типом интерфейса I (§9.1.4), охватывает все тело I, включая любые объявления вложенных типов.
Область действия константы перечисления C, объявленной в типе перечисления T, — это тело T, и любой case метки switch оператора, выражение которого имеет тип перечисления T.
Область действия формального параметра метода (§8.4.1) или конструктора (§8.8.1) — это все тело метода или конструктора.
Область действия параметра типа класса (§8.1.2) — это раздел параметров типа объявления класса, раздел параметров типа любого суперкласса или суперинтерфейса объявления класса и тело класса.
Область действия параметра типа интерфейса (§9.1.2) — это раздел параметров типа объявления интерфейса, раздел параметров типа любого суперинтерфейса объявления интерфейса и тело интерфейса.
Область действия параметра типа метода (§8.4.4) — это все объявление метода, включая раздел параметров типа, но исключая модификаторы метода.
Область действия параметра типа конструктора (§8.8.4) — это все объявление конструктора, включая раздел параметров типа, но исключая модификаторы конструктора.
Область действия объявления локального класса, непосредственно заключенного в блок (§14.2), — это остальная часть непосредственно окружающего блока, включая собственное объявление класса.
Область действия объявления локального класса, непосредственно заключенного в группу операторов блока switch (§14.11), — это остальная часть непосредственно окружающего группы операторов блока switch, включая собственное объявление класса.
Область действия объявления локальной переменной в блоке (§14.4) — это остальная часть блока, в котором появляется объявление, начиная с собственной инициализации и включая любые другие деклараторы справа в операторе объявления локальной переменной.
Область действия локальной переменной, объявленной в части ForInit оператора цикла for (§14.14.1) включает все нижеследующее:
-
Собственная инициализация
-
Любые другие деклараторы справа в части ForInit оператора цикла for
-
Части Expression и ForUpdate оператора цикла for
-
Содержащийся оператор Statement
Область действия локальной переменной, объявленной в части FormalParameter оператора цикла for-each (§14.14.2), — это содержащийся оператор Statement.
Область действия параметра обработчика исключений, который объявлен в блоке catch оператора try (§14.20), — это весь блок, связанный с catch-блоком.
Область действия переменной, объявленной в ResourceSpecification оператора try-with-resources (§14.20.3), — от объявления вправо до остальной части ResourceSpecification и всего блока try, связанного с оператором try-with-resources.
Перевод оператора try-with-resources подразумевает вышеприведенное правило.
Пример 6.3-1. Область действия и объявления типов
Эти правила подразумевают, что объявления типов классов и интерфейсов не должны появляться перед использованием этих типов. В следующей программе использование PointList в классе Point допустимо, потому что область действия объявления класса PointList включает в себя как класс Point, так и класс PointList, а также любые другие объявления типов в других единицах компиляции пакета points.
package points;
class Point {
int x, y;
PointList list;
Point next;
}
class PointList {
Point first;
}
Пример 6.3-2. Область действия объявления локальной переменной
class Test1 {
static int x;
public static void main(String[] args) {
int x = x;
}
}
Эта программа вызывает ошибку компиляции, потому что инициализация x находится в области действия объявления x как локальной переменной, и локальная переменная x еще не имеет значения и не может быть использована.
Следующая программа компилируется:
class Test2 {
static int x;
public static void main(String[] args) {
int x = (x=2)*2;
System.out.println(x);
}
}
потому что локальной переменной x определенно присваивается значение (§16) перед её использованием. Она выводит:
4
В следующей программе инициализатор для three может правильно ссылаться на переменную two, объявленную в предыдущем деклараторе, и вызов метода в следующей строке может правильно ссылаться на переменную three, объявленную ранее в блоке.
class Test3 {
public static void main(String[] args) {
System.out.print("2+1=");
int two = 2, three = two + 1;
System.out.println(three);
}
}
Эта программа выводит:
2+1=3
Локальная переменная (§14.4), формальный параметр (§8.4.1), параметр исключения (§14.20) и локальный класс (§14.3) могут ссылаться только с использованием простого имени (§6.2), а не с квалифицированным именем (§6.6).
Некоторые объявления запрещены в области действия локальной переменной, формального параметра, параметра исключения или объявления локального класса, потому что невозможно отличить объявленные сущности, используя только простые имена.
Например, если имя формального параметра метода может быть повторно объявлено как имя локальной переменной в теле метода, то локальная переменная будет затемнять формальный параметр, и формальный параметр больше не будет виден — нежелательный результат.
Ошибка компиляции, если имя формального параметра повторно объявляется как локальная переменная метода или конструктора; или как параметр исключения в catch-оператор в try операторе в теле метода или конструктора; или как ресурс в операторе try-с-ресурсами в теле метода или конструктора.
Ошибка компиляции, если имя локальной переменной v повторно объявляется как локальная переменная непосредственно окружающего метода, конструктора или блока инициализации в области действия v; или как параметр исключения в catch-оператор в try-оператор непосредственно окружающего метода, конструктора или блока инициализации в области действия v; или как ресурс в операторе try-с-ресурсами непосредственно окружающего метода, конструктора или блока инициализации в области действия v.
Ошибка компиляции, если имя локального класса C повторно объявляется как локальный класс непосредственно окружающего метода, конструктора или блока инициализации в области действия C.
Ошибка компиляции, если имя параметра исключения повторно объявляется в блоке Блок catch-оператор в качестве локальной переменной непосредственно окружающего метода, конструктора или блока инициализации; или как параметр исключения в catch-оператор в try-оператор непосредственно окружающего метода, конструктора или блока инициализации; или как ресурс в операторе try-с-ресурсами непосредственно окружающего метода, конструктора или блока инициализации.
Ошибка компиляции, если имя переменной, объявленной в ResourceSpecification операторе try-с-ресурсами (§14.20.3), повторно объявляется в блоке try Блок как локальная переменная непосредственно окружающего метода, конструктора или блока инициализации, или как параметр исключения в catch-оператор в try-оператор непосредственно окружающего метода или блока инициализации.
Перевод оператора try-с-ресурсами подразумевает правило выше.
Несмотря на правила против повторного объявления переменных, правила затемнения (§6.4.1) допускают повторное объявление в определенных объявлениях вложенных классов (т.е. локальных классов (§14.3) и анонимных классов (§15.9)) следующим образом:
-
Формальный параметр метода или конструктора может быть затемнён где-либо внутри объявления класса, вложенного в этот метод или конструктор.
-
Локальная переменная метода, конструктора или блока инициализации может быть затемнена где-либо внутри объявления класса, вложенного в область действия локальной переменной.
-
Объявление локального класса может быть затемнено где-либо внутри объявления класса, вложенного в область действия объявления локального класса.
-
Параметр исключения может быть затемнён где-либо внутри объявления класса, вложенного в Блок
catch-оператор. -
Переменная, объявленная в ResourceSpecification, может быть затемнена где-либо внутри объявления класса, вложенного в
tryБлок.
Пример 6.4-1. Попытка затемнения локальной переменной
Поскольку объявление идентификатора как локальной переменной метода, конструктора или блока инициализации не должно появляться в области действия параметра или локальной переменной с тем же именем, для следующей программы возникает ошибка компиляции:
class Test1 {
public static void main(String[] args) {
int i;
for (int i = 0; i < 10; i++)
System.out.println(i);
}
}
Это ограничение помогает обнаруживать некоторые в противном случае очень скрытые ошибки. Аналогичное ограничение на затемнение членов локальными переменными было признано непрактичным, так как добавление члена в суперкласс может привести к тому, что подклассы должны переименовывать локальные переменные. Соответствующие соображения делают нежелательными ограничения на затемнение локальных переменных членами вложенных классов или на затемнение локальных переменных локальными переменными, объявленными в вложенных классах.
Следовательно, следующая программа компилируется без ошибок:
class Test2 {
public static void main(String[] args) {
int i;
class Local {
{
for (int i = 0; i < 10; i++)
System.out.println(i);
}
}
new Local();
}
}
С другой стороны, локальные переменные с одинаковым именем могут быть объявлены в двух отдельных блоках или операторах for, ни один из которых не содержит другого:
class Test3 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++)
System.out.print(i + " ");
for (int i = 10; i > 0; i--)
System.out.print(i + " ");
System.out.println();
}
}
Эта программа компилируется без ошибок и при выполнении выводит:
0 1 2 3 4 5 6 7 8 9 10 9 8 7 6 5 4 3 2 1
Некоторые объявления могут быть затенены в части своего области видимости другим объявлением с тем же именем, в этом случае простое имя нельзя использовать для обращения к объявленной сущности.
Затенение отличается от скрытия (§8.3, §8.4.8.2, §8.5, §9.3, §9.5), которое применяется только к членам, которые в противном случае были бы унаследованы, но не являются таковыми из-за объявления в подклассе. Затенение также отличается от затемнения (§6.4.2).
Обьявление d считается видимым в точке p в программе, если область видимости d включает p, и d не затеняется ни одним другим объявлением в p.
Когда точка программы, о которой идет речь, ясна из контекста, мы часто будем просто говорить, что объявление видимо.
Объявление d типа с именем n затеняет объявления любых других типов с именем n, которые находятся в области видимости в точке, где происходит d, в течение области видимости d.
Объявление d поля или формального параметра с именем n затеняет, в течение области видимости d, объявления любых других переменных с именем n, которые находятся в области видимости в точке, где происходит d.
Объявление d локальной переменной или параметра исключения с именем n затеняет, в течение области видимости d, (а) объявления любых других полей с именем n, которые находятся в области видимости в точке, где происходит d, и (б) объявления любых других переменных с именем n, которые находятся в области видимости в точке, где происходит d, но не объявлены в самом внутреннем классе, в котором объявлено d.
Объявление d метода с именем n затеняет объявления любых других методов с именем n, которые находятся во внешней области видимости в точке, где происходит d, на протяжении всей области видимости d.
Объявление пакета никогда не затеняет ни одного другого объявления.
Объявление импорта типа по требованию никогда не приводит к затенению другого объявления.
Объявление статического импорта по требованию никогда не приводит к затенению другого объявления.
Объявление импорта одного типа d в единице компиляции c пакета p, который импортирует тип с именем n, затеняет, во всей c, объявления:
-
любой тип верхнего уровня с именем n, объявленный в другой единице компиляции пакета p
-
любой тип с именем n, импортированный объявлением импорта типа по требованию в c
-
любой тип с именем n, импортированный объявлением статического импорта по требованию в c
Объявление импорта одного статического d d в единице компиляции c пакета p, которое импортирует поле с именем n, затеняет объявление любого статического поля с именем n, импортированного объявлением статического импорта по требованию в c, во всей c.
Объявление импорта одного статического d d в единице компиляции c пакета p, которое импортирует метод с именем n со сигнатурой s, затеняет объявление любого статического метода с именем n со сигнатурой s, импортированного объявлением статического импорта по требованию в c, во всей c.
Объявление импорта одного статического d d в единице компиляции c пакета p, которое импортирует тип с именем n, затеняет, во всей c, объявления:
Пример 6.4.1-1. Затенение объявления поля локальной переменной
class Test {
static int x = 1;
public static void main(String[] args) {
int x = 0;
System.out.print("x=" + x);
System.out.println(", Test.x=" + Test.x);
}
}
Эта программа выводит:
x=0, Test.x=1
Эта программа объявляет:
-
класс
Test -
переменную класса (
static)x, которая является членом классаTest -
метод класса
main, который является членом классаTest -
параметр
argsметодаmain -
локальную переменную
xметодаmain
Поскольку область видимости переменной класса включает весь тело класса (§8.2), переменная класса x обычно доступна на протяжении всего тела метода main. Однако в этом примере переменная класса x затенена внутри тела метода main объявлением локальной переменной x.
Локальная переменная имеет в качестве своей области видимости остальную часть блока, в котором она объявлена (§6.3); в данном случае это остальная часть тела метода main, а именно его инициализатор "0" и вызовы System.out.print и System.out.println.
Это означает, что:
-
выражение
xв вызовеprintссылается на (обозначает) значение локальной переменнойx. -
вызов
printlnиспользует полное имя (§6.6)Test.x, которое использует имя типа классаTestдля доступа к переменной классаx, потому что объявлениеTest.xзатенено в этой точке и не может быть обращено по его простому имени.
Ключевое слово this также может использоваться для доступа к затенённому полю x, используя форму this.x. Действительно, эта идиома обычно встречается в конструкторах (§8.8):
class Pair {
Object first, second;
public Pair(Object first, Object second) {
this.first = first;
this.second = second;
}
}
Здесь конструктор принимает параметры с теми же именами, что и поля, которые необходимо инициализировать. Это проще, чем придумывать разные имена для параметров, и не слишком запутанно в этом стилизованном контексте. Однако, в целом, считается плохим стилем иметь локальные переменные с теми же именами, что и поля.
Пример 6.4.1-2. Затенение объявления типа другим объявлением типа
import java.util.*;
class Vector {
int val[] = { 1 , 2 };
}
class Test {
public static void main(String[] args) {
Vector v = new Vector();
System.out.println(v.val[0]);
}
}
Программа компилируется и печатает:
1
используя объявленный здесь класс Vector вместо обобщенного класса java.util.Vector (§8.1.2), который может быть импортирован по требованию.
Простое имя может встречаться в контекстах, где оно потенциально может быть интерпретировано как имя переменной, типа или пакета. В этих ситуациях правила §6.5 указывают, что переменная будет выбрана в приоритете перед типом, а тип — в приоритете перед пакетом. Таким образом, иногда может быть невозможно сослаться на видимый тип или объявление пакета через его простое имя. Мы говорим, что такое объявление затемнено.
Затемнение отличается от затенения (§6.4.1) и скрытия (§8.3, §8.4.8.2, §8.5, §9.3, §9.5).
Правила именования в §6.1 помогают снизить затемнение, но если это происходит, вот несколько примечаний о том, что можно сделать, чтобы избежать его.
Когда имена пакетов встречаются в выражениях:
-
Если имя пакета затемнено объявлением поля, то
importобъявления (§7.5) обычно могут использоваться для доступа к именам типов, объявленным в этом пакете. -
Если имя пакета затемнено объявлением параметра или локальной переменной, то имя параметра или локальной переменной может быть изменено без влияния на другой код.
Первый компонент имени пакета обычно не легко путается с именем типа, так как имя типа обычно начинается с заглавной буквы. (Язык программирования Java фактически не полагается на различия в регистре, чтобы определить, является ли имя именем пакета или именем типа.)
Затемнение, связанное с именами типов классов и интерфейсов, встречается редко. Имена полей, параметров и локальных переменных обычно не затемняют имена типов, потому что они по соглашению начинаются с маленькой буквы, в то время как имена типов по соглашению начинаются с заглавной буквы.
Имена методов не могут затемнять или быть затемнены другими именами (§6.5.7).
Затемнение, связанное с именами полей, встречается редко; однако:
-
Если имя поля затемняет имя пакета, то объявление
import(§7.5) обычно может использоваться для доступа к именам типов, объявленным в этом пакете. -
Если имя поля затемняет имя типа, то можно использовать полностью квалифицированное имя типа, за исключением случаев, когда имя типа обозначает локальный класс (§14.3).
-
Имена полей не могут затемнять имена методов.
-
Если имя поля затенено объявлением параметра или локальной переменной, то имя параметра или локальной переменной может быть изменено без влияния на другой код.
Затемнение, связанное с именами констант, встречается редко:
-
Имена констант обычно не содержат строчных букв, поэтому они обычно не будут затемнять имена пакетов или типов, а также обычно не будут затемнять поля, имена которых, как правило, содержат по крайней мере одну строчную букву.
-
Имена констант не могут затемнять имена методов, так как они различаются синтаксически.
Значение имени зависит от контекста его использования. Определение значения имени требует трех шагов:
-
Во-первых, контекст заставляет имя синтаксически принадлежать к одной из шести категорий: ИмяПакеты, ИмяТип, ИмяВыражения, ИмяМетода, ИмяПакетыИлиТип или НеоднозначноеИмя.
-
Во-вторых, имя, изначально классифицированное своим контекстом как НеоднозначноеИмя или как ИмяПакетыИлиТип, затем переклассифицируется как ИмяПакеты, ИмяТип или ИмяВыражения.
-
В-третьих, полученная категория затем определяет окончательное определение значения имени (или ошибку компиляции, если у имени нет смысла).
ИмяПакеты:
Идентификатор
ИмяПакеты . Идентификатор
ИмяТип:
Идентификатор
ИмяПакетыИлиТип . Идентификатор
ИмяВыражения:
Идентификатор
НеоднозначноеИмя . Идентификатор
ИмяМетода:
Идентификатор
НеоднозначноеИмя . Идентификатор
ИмяПакетыИлиТип:
Идентификатор
ИмяПакетыИлиТип . Идентификатор
НеоднозначноеИмя:
Идентификатор
НеоднозначноеИмя . Идентификатор
Использование контекста помогает свести к минимуму конфликты имен между сущностями разных типов. Такие конфликты будут редки, если будут соблюдены правила именования, описанные в §6.1. Тем не менее, конфликты могут возникать непреднамеренно, по мере развития типов, разработанных разными программистами или разными организациями. Например, типы, методы и поля могут иметь одинаковые имена. Всегда возможно различить метод и поле с одинаковым именем, так как контекст использования всегда указывает, является ли метод предназначенным.
Имя синтаксически классифицируется как PackageName в следующих контекстах:
-
В объявлении пакета (§7.4)
-
Слева от "
." в квалифицированном PackageName
Имя синтаксически классифицируется как TypeName в следующих контекстах:
-
В объявлении импорта одного типа (§7.5.1)
-
Слева от "
." в объявлении импорта одного статического члена (§7.5.3) -
Слева от "
." в объявлении импорта всех статических членов (§7.5.4) -
Слева от "
<" в параметризованном типе (§4.5) -
В списке аргументов типа (§4.5.1) параметризованного типа
-
В явном списке аргументов типа в вызове метода или конструктора
-
В предложении
extendsв объявлении переменной типа (§8.1.2) -
В предложении
extendsаргумента типа с подстановкой (§4.5.1) -
В предложении
superаргумента типа с подстановкой (§4.5.1) -
В предложении
extendsв объявлении класса (§8.1.4) -
В предложении
implementsв объявлении класса (§8.1.5) -
В предложении
extendsв объявлении интерфейса (§9.1.3) -
После знака "
@" в аннотации (§9.7) -
В качестве Type (или части Type, которая остаётся после удаления всех скобок) в любом из следующих контекстов:
-
В качестве типа формального параметра метода или конструктора (§8.4.1, §8.8.1, §9.4)
-
В качестве типа исключения, которое может быть выброшено методом или конструктором (§8.4.6, §8.8.5, §9.4)
-
В качестве типа локальной переменной (§14.4)
-
В качестве типа параметра исключения в предложении
catchоператораtry(§14.20) -
В качестве типа в литерале класса (§15.8.2)
-
В качестве квалифицирующего типа квалифицированного
thisвыражения (§15.8.4). -
В качестве типа класса, который должен быть создан в выражении создания экземпляра класса без квалификатора (§15.9)
-
В качестве непосредственного суперкласса или непосредственного суперинтерфейса анонимного класса (§15.9.5), который должен быть создан в выражении создания экземпляра класса без квалификатора (§15.9)
-
В качестве типа элемента массива, который должен быть создан в выражении создания массива (§15.10)
-
В качестве квалифицирующего типа доступа к полю с использованием ключевого слова
super(§15.11.2) -
В качестве квалифицирующего типа вызова метода с использованием ключевого слова
super(§15.12) -
В качестве типа, указанного в операторе приведения типа выражения приведения типа (§15.16)
-
В качестве типа, следующего за оператором сравнения
instanceof(§15.20.2)
Имя синтаксически классифицируется как ExpressionName в следующих контекстах:
-
В качестве квалифицирующего выражения в квалифицированном вызове конструктора суперкласса (§8.8.7.1)
-
В качестве квалифицирующего выражения в квалифицированном выражении создания экземпляра класса (§15.9)
-
В качестве выражения ссылки на массив в выражении доступа к элементу массива (§15.13)
-
В качестве PostfixExpression (§15.14)
-
В качестве левого операнда оператора присваивания (§15.26)
Имя синтаксически классифицируется как MethodName в следующих контекстах:
Имя синтаксически классифицируется как PackageOrTypeName в следующих контекстах:
-
Слева от "
." в квалифицированном TypeName -
В объявлении импорта всех типов (§7.5.2)
Имя синтаксически классифицируется как AmbiguousName в следующих контекстах:
Результат синтаксической классификации заключается в ограничении определенных типов сущностей определенными частями выражений:
-
Имя поля, параметра или локальной переменной может использоваться в качестве выражения (§15.14.1).
-
Имя метода может встречаться в выражении только как часть выражения вызова метода (§15.12).
-
Имя типа класса или интерфейса может встречаться в выражении только как часть литерала класса (§15.8.2), квалифицированного
thisвыражения (§15.8.4), выражения создания экземпляра класса (§15.9), выражения создания массива (§15.10), выражения приведения типа (§15.16),instanceofвыражения (§15.20.2), константы перечисления (§8.9) или в качестве части квалифицированного имени для поля или метода. -
Имя пакета может встречаться в выражении только как часть квалифицированного имени для типа класса или интерфейса.
Тогда НеоднозначноеИмя переклассифицируется следующим образом.
Если НеоднозначноеИмя — простое имя, состоящее из одного Идентификатора:
-
Если Идентификатор появляется в области видимости (§6.3) объявления локальной переменной (§14.4) или параметра (§8.4.1, §8.8.1, §14.20) или объявления поля (§8.3) с этим именем, то НеоднозначноеИмя переклассифицируется как ИмяВыражения.
-
В противном случае, если поле с таким именем объявлено в единице компиляции (§7.3), содержащей Идентификатор, с помощью объявления одного статического импорта (§7.5.3) или объявления статического импорта по требованию (§7.5.4), то НеоднозначноеИмя переклассифицируется как ИмяВыражения.
-
В противном случае, если Идентификатор появляется в области видимости (§6.3) объявления класса верхнего уровня (§8) или интерфейса (§9), объявления локального класса (§14.3) или объявления вложенного типа (§8.5, §9.5) с этим именем, то НеоднозначноеИмя переклассифицируется как ИмяТипа.
-
В противном случае, если тип с таким именем объявлен в единице компиляции (§7.3), содержащей Идентификатор, с помощью объявления одного импорта типа (§7.5.1) или объявления импорта типов по требованию (§7.5.2) или объявления одного статического импорта (§7.5.3) или объявления статического импорта по требованию (§7.5.4), то НеоднозначноеИмя переклассифицируется как ИмяТипа.
-
В противном случае НеоднозначноеИмя переклассифицируется как ИмяПакета. На более позднем этапе определяется, существует ли пакет с таким именем.
Если НеоднозначноеИмя — квалифицированное имя, состоящее из имени, ".", и Идентификатора, то имя слева от "." сначала переклассифицируется, так как оно само является НеоднозначноеИмя. Затем возникает выбор:
-
Если имя слева от "
." переклассифицируется как ИмяПакета, то:-
Если существует пакет с именем, равным имени слева от "
.", и этот пакет содержит объявление типа с именем, совпадающим с Идентификатором, то это НеоднозначноеИмя переклассифицируется как ИмяТипа. -
В противном случае это НеоднозначноеИмя переклассифицируется как ИмяПакета. На более позднем этапе определяется, существует ли пакет с таким именем.
-
-
Если имя слева от "
." переклассифицируется как ИмяТипа, то:-
Если Идентификатор — имя метода или поля типа, обозначаемого ИмяТипа, то это НеоднозначноеИмя переклассифицируется как ИмяВыражения.
-
В противном случае, если Идентификатор — имя вложенного типа типа, обозначаемого ИмяТипа, то это НеоднозначноеИмя переклассифицируется как ИмяТипа.
-
В противном случае возникает ошибка времени компиляции.
-
-
Если имя слева от "
." переклассифицируется как ИмяВыражения, то пусть T — тип выражения, обозначаемого ИмяВыражения.-
Если Идентификатор — имя метода или поля типа, обозначаемого T, то это НеоднозначноеИмя переклассифицируется как ИмяВыражения.
-
В противном случае, если Идентификатор — имя типа-члена (§8.5, §9.5) типа, обозначаемого T, то это НеоднозначноеИмя переклассифицируется как ИмяТипа.
-
В противном случае возникает ошибка времени компиляции.
-
Пример 6.5.2-1. Переклассификация контекстуально неоднозначных имён
Рассмотрим следующий выдуманный "код библиотеки":
package org.rpgpoet;
import java.util.Random;
public interface Music { Random[] wizards = new Random[4]; }
а затем рассмотрим этот пример кода в другом пакете:
package bazola;
class Gabriel {
static int n = org.rpgpoet.Music.wizards.length;
}
В первую очередь имя org.rpgpoet.Music.wizards.length классифицируется как ИмяВыражения, так как оно действует как ПостфиксноеВыражение. Следовательно, каждое из имён:
org.rpgpoet.Music.wizards org.rpgpoet.Music org.rpgpoet org
изначально классифицируется как НеоднозначноеИмя. Затем эти имена переклассифицируются:
-
Простое имя
orgпереклассифицируется как ИмяПакета (поскольку переменная или тип org не находятся в области видимости). -
Далее, предполагая, что нет класса или интерфейса с именем
rpgpoetни в одной единице компиляции пакетаorg(и мы знаем, что такого класса или интерфейса нет, потому что пакетorgимеет подпакет с именемrpgpoet), квалифицированное имяorg.rpgpoetпереклассифицируется как ИмяПакета. -
Далее, так как пакет
org.rpgpoetимеет доступный (§6.6) интерфейс типа с именемMusic, квалифицированное имяorg.rpgpoet.Musicпереклассифицируется как ИмяТипа. -
Наконец, поскольку имя
org.rpgpoet.Music— ИмяТипа, квалифицированное имяorg.rpgpoet.Music.wizardsпереклассифицируется как ИмяВыражения.
Значение имени, классифицированного как PackageName, определяется следующим образом.
Если имя пакета состоит из одного Identifier, то этот идентификатор обозначает пакет верхнего уровня, названный этим идентификатором.
Если пакет верхнего уровня с таким именем не находится в области видимости (§6.3), то возникает ошибка времени компиляции.
Если имя пакета имеет вид Q.Id, то Q также должно быть именем пакета. Имя пакета Q.Id обозначает пакет, который является членом, имеющим имя Id, внутри пакета, обозначаемого именем Q.
Если Q не обозначает наблюдаемый пакет (§7.4.3), или Id не является простым именем наблюдаемого подпакета этого пакета, то возникает ошибка времени компиляции.
Если PackageOrTypeName, Q, встречается в области видимости типа с именем Q, то PackageOrTypeName переклассифицируется как TypeName.
В противном случае PackageOrTypeName переклассифицируется как PackageName. Значение PackageOrTypeName — это значение переклассифицированного имени.
Для квалифицированного PackageOrTypeName вида Q.Id, если тип или пакет, обозначаемый Q, имеет тип-член с именем Id, то квалифицированное имя PackageOrTypeName переклассифицируется как TypeName.
В противном случае оно переклассифицируется как PackageName. Значение квалифицированного PackageOrTypeName — это значение переклассифицированного имени.
Значение имени, классифицированного как TypeName, определяется следующим образом.
Если имя типа состоит из одного Identifier, то этот идентификатор должен встречаться в области видимости ровно одного видимого объявления типа с таким именем; в противном случае возникает ошибка времени компиляции. Значение имени типа — это этот тип.
Если имя типа имеет вид Q.Id, то Q должно быть именем типа или именем пакета.
Если Id обозначает ровно один доступный тип (§6.6), который является членом типа или пакета, обозначаемого Q, то квалифицированное имя типа обозначает этот тип.
Если Id не обозначает тип-член (§8.5, §9.5) в Q, или не существует ровно одного доступного (§6.6) типа-члена с именем Id в Q, или Id обозначает статический тип-член (§8.5.1) в Q, и Q параметризован, то возникает ошибка времени компиляции.
Пример 6.5.5.2-1. Квалифицированные имена типов
class Test {
public static void main(String[] args) {
java.util.Date date =
new java.util.Date(System.currentTimeMillis());
System.out.println(date.toLocaleString());
}
}
При первом запуске эта программа вывела следующий результат:
Sun Jan 21 22:56:29 1996
В этом примере имя java.util.Date должно обозначать тип, поэтому мы сначала используем рекурсивную процедуру, чтобы определить, является ли java.util доступным типом или пакетом, чем оно и является, а затем проверяем, доступен ли тип Date в этом пакете.
Значение имени, классифицированного как ExpressionName, определяется следующим образом.
Если имя выражения состоит из одного Identifier, то должно быть ровно одно объявление, обозначающее локальную переменную, параметр или поле, видимое (§6.4.1) в точке, где встречается Identifier. В противном случае возникает ошибка времени компиляции.
Если объявление обозначает переменную экземпляра (§8.3), имя выражения должно появляться внутри объявления метода экземпляра (§8.4), конструктора (§8.8), инициализатора экземпляра (§8.6) или инициализатора переменной экземпляра (§8.3.2.2). Если имя выражения появляется внутри статического метода (§8.4.3.2), статического инициализатора (§8.7) или инициализатора статической переменной (§8.3.2.1, §12.4.2), то возникает ошибка времени компиляции.
Если объявление объявляет final переменную, которая определённо присвоена до простого выражения, значение имени — это значение этой переменной. В противном случае значение имени выражения — это переменная, объявленная в объявлении.
Если имя выражения появляется в контексте, где оно подвержено преобразованию присваивания, вызова метода или преобразованию приведения типов, то тип имени выражения — это объявленный тип поля, локальной переменной или параметра после преобразования захвата (§5.1.10).
В противном случае, тип имени выражения — это объявленный тип поля, локальной переменной или параметра.
То есть, если имя выражения появляется «справа от знака равенства», его тип подвергается преобразованию захвата. Если имя выражения является переменной, которая появляется «слева от знака равенства», его тип не подвергается преобразованию захвата.
Пример 6.5.6.1-1. Простые имена выражений
class Test {
static int v;
static final int f = 3;
public static void main(String[] args) {
int i;
i = 1;
v = 2;
f = 33; // compile-time error
System.out.println(i + " " + v + " " + f);
}
}
В этой программе имена, используемые в левой части присваиваний к i, v и f, обозначают локальную переменную i, поле v и значение f (а не переменную f, потому что f — это переменная final). Поэтому в примере возникает ошибка времени компиляции, потому что в последнем присваивании нет переменной в левой части. Если ошибочное присваивание удалить, изменённый код можно скомпилировать, и он выведет:
1 2 3
Если имя выражения имеет вид Q.Id, то Q уже классифицировано как имя пакета, имя типа или имя выражения.
Если Q — имя пакета, то возникает ошибка времени компиляции.
Если Q — имя типа, обозначающее тип класса (§8), то:
-
Если нет ровно одного доступного (§6.6) члена типа класса, являющегося полем с именем
Id, то возникает ошибка времени компиляции. -
В противном случае, если единственное доступное поле-член не является статической переменной (то есть, оно не объявлено
static), то возникает ошибка времени компиляции. -
В противном случае, если статическая переменная объявлена
final, тоQ.Idобозначает значение статической переменной.Тип выражения
Q.Id— это объявленный тип статической переменной после преобразования захвата (§5.1.10).Если
Q.Idпоявляется в контексте, требующем переменной, а не значения, возникает ошибка времени компиляции. -
В противном случае,
Q.Idобозначает статическую переменную.Тип выражения
Q.Id— это объявленный тип статической переменной после преобразования захвата (§5.1.10).Обратите внимание, что этот пункт охватывает использование
enumконстант (§8.9), так как они всегда имеют соответствующуюfinalстатическую переменную.
Если Q — имя типа, обозначающее тип интерфейса (§9), то:
-
Если нет ровно одного доступного (§6.6) члена типа интерфейса, являющегося полем с именем
Id, то возникает ошибка времени компиляции. -
В противном случае,
Q.Idобозначает значение поля.Тип выражения
Q.Id— это объявленный тип поля после преобразования захвата (§5.1.10).Если
Q.Idпоявляется в контексте, требующем переменную, а не значение, возникает ошибка времени компиляции.
Если Q — имя выражения, пусть T — тип выражения Q:
-
Если T не является ссылочным типом, возникает ошибка времени компиляции.
-
Если нет ровно одного доступного (§6.6) члена типа T, являющегося полем с именем
Id, то возникает ошибка времени компиляции. -
В противном случае, если это поле — любое из следующего:
-
Поле типа интерфейса
-
Поле
finalтипа класса (которое может быть либо статической переменной, либо переменной экземпляра) -
Поле
finallengthтипа массива
то
Q.Idобозначает значение поля, если только оно не появляется в контексте, требующем переменную, и поле — это определённо неинициализированное пустоеfinalполе, в котором случае оно возвращает переменную.Тип выражения
Q.Id— это объявленный тип поля после преобразования захвата (§5.1.10).Если
Q.Idпоявляется в контексте, требующем переменную, а не значение, и поле, обозначенноеQ.Id, определённо присвоено, то возникает ошибка времени компиляции. -
-
В противном случае,
Q.Idобозначает переменную, полеIdкласса T, которое может быть либо статической переменной, либо переменной экземпляра.Тип выражения
Q.Id— это тип поля-члена после преобразования захвата (§5.1.10).
Пример 6.5.6.2-1. Квалифицированные имена выражений
class Point {
int x, y;
static int nPoints;
}
class Test {
public static void main(String[] args) {
int i = 0;
i.x++; // compile-time error
Point p = new Point();
p.nPoints(); // compile-time error
}
}
Эта программа сталкивается с двумя ошибками времени компиляции, так как у переменной int i нет членов, а также потому, что nPoints не является методом класса Point.
Пример 6.5.6.2-2. Квалификация выражения именем типа
Обратите внимание, что имена выражений могут быть квалифицированы именами типов, но не типами вообще. Следствием этого является то, что невозможно получить доступ к статической переменной через параметризованный тип.
Например, приведённый код:
class Foo<T> {
public static int classVar = 42;
}
следующее присваивание является незаконным:
Foo<String>.classVar = 91; // illegal
Вместо этого следует написать
Foo.classVar = 91;
Это не ограничивает язык программирования Java каким-либо осмысленным образом. Параметры типа не могут использоваться в типах статических переменных, и поэтому аргументы типа параметризованного типа никогда не могут влиять на тип статической переменной. Поэтому выразительная сила не теряется. Технически, имя типа Foo выше является необработанным типом, но это использование необработанных типов безопасно и не приводит к предупреждениям.
Значение имени, классифицированного как ИмяМетода, определяется следующим образом.
Простое имя метода может выступать в качестве имени элемента в паре «имя элемента — значение».
В этом случае Идентификатор в ПареИмяЭлементЗначение должен быть простым именем одного из элементов типа аннотации, идентифицированного ИменованиемТипа в содержащей аннотации, иначе произойдёт ошибка компиляции.
Другими словами, идентификатор в паре «имя элемента — значение» также должен быть именем метода в интерфейсе, идентифицированном ИменованиемТипа.
В противном случае простое имя метода обязательно появляется в контексте выражения вызова метода. В этом случае, если имя метода состоит из одного Идентификатора, то Идентификатор является именем метода, используемым для вызова метода.
Идентификатор должен обозначать как минимум один метод, видимый в точке, где появляется Идентификатор (§6.4.1), или импортированный методом объявления single-static-import (§7.5.3) или объявления static-import-on-demand (§7.5.4) в единице компиляции, в которой появляется Идентификатор, в противном случае произойдёт ошибка компиляции по правилам §15.12.
Пример 6.5.7.1-1. Простые имена методов и видимость
Следующая программа демонстрирует роль видимости метода при определении вызываемого метода.
class Super {
void f2(String s) {}
void f3(String s) {}
void f3(int i1, int i2) {}
}
class Test {
void f1(int i) {}
void f2(int i) {}
void f3(int i) {}
void m() {
new Super() {
{
f1(0); // OK, resolves to Test.f1(int)
f2(0); // compile-time error
f3(0); // compile-time error
}
};
}
}
Для вызова f1(0) виден только один метод с именем f1. Это метод Test.f1(int), объявление которого находится в области действия во всем теле Test, включая объявление анонимного класса. §15.12.1 выбирает поиск в классе Test, так как у анонимного класса нет члена с именем f1. В конечном итоге, Test.f1(int) разрешается.
Для вызова f2(0) видны два метода с именем f2. Во-первых, объявление метода Super.f2(String) находится в области действия во всем анонимном классе. Во-вторых, объявление метода Test.f2(int) находится в области действия во всём теле Test, включая объявление анонимного класса. §15.12.1 выбирает поиск в классе Super, потому что у него есть член с именем f2. Однако, Super.f2(String) не применим к f2(0), поэтому возникает ошибка компиляции. Обратите внимание, что класс Test не ищется.
Для вызова f3(0) видны три метода с именем f3. Во-первых и во-вторых, объявления методов Super.f3(String) и Super.f3(int,int) находятся в области действия во всём анонимном классе. В-третьих, объявление метода Test.f3(int) находится в области действия во всём теле Test, включая объявление анонимного класса. §15.12.1 выбирает поиск в классе Super, потому что у него есть член с именем f3. Однако, Super.f3(String) и Super.f3(int,int) не применимы к f3(0), поэтому возникает ошибка компиляции. Обратите внимание, что класс Test не ищется.
Выбор поиска в иерархии суперклассов вложенного класса перед лексически окружающим пространством называется «правилом гребня» (§15.12.1).
Квалифицированное имя метода может появляться только в контексте выражения вызова метода.
Если имя метода имеет вид Q.Id, то Q уже классифицировано как имя пакета, имя типа или имя выражения:
-
Если
Q— имя пакета, то произойдёт ошибка компиляции. -
В противном случае
Q— имя типа или имя выражения.Id— это имя метода, которое будет использоваться для вызова метода.Если
Q— имя типа, тоIdдолжен обозначать как минимум один метод типаQ, иначе произойдёт ошибка компиляции по правилам §15.12.Если
Q— имя выражения, то пусть T будет типом выраженияQ.Idдолжен обозначать как минимум один метод типа T, иначе произойдёт ошибка компиляции по правилам §15.12.
Как и имена выражений, имена методов могут быть квалифицированы именами типов, но не типами в общем. Последствия аналогичны тем, которые обсуждаются для имён выражений в §6.5.6.2.
Язык программирования Java предоставляет механизмы управления доступом, чтобы предотвратить зависимость пользователей от пакета или класса от ненужных деталей реализации этого пакета или класса. Если доступ разрешен, то доступный элемент называется доступным.
Обратите внимание, что доступность — это статическое свойство, которое может быть определено на этапе компиляции; оно зависит только от типов и модификаторов объявления.
Квалифицированные имена являются средством доступа к членам пакетов и ссылочных типов. Когда имя такого члена классифицируется из контекста (§6.5.1) как квалифицированное имя типа (обозначающее член пакета или ссылочного типа, §6.5.5.2) или квалифицированное имя выражения (обозначающее член ссылочного типа, §6.5.6.2), применяется контроль доступа.
Например, оператор импорта одного типа (§7.5.1) должен использовать квалифицированное имя типа, поэтому имя импортируемого типа должно быть доступно из единицы компиляции, содержащей оператор импорта. Другой пример: объявление класса может использовать квалифицированное имя типа для суперкласса (§8.1.5), и вновь квалифицированное имя типа должно быть доступно.
Некоторые очевидные выражения «отсутствуют» в классификации контекста в §6.5.1: доступ к полю в Первичном элементе (§15.11.1), вызов метода в Первичном элементе (§15.12) и экземпляр созданного класса в квалифицированном создании экземпляра класса (§15.9). Каждое из этих выражений использует идентификаторы, а не имена, по причине, указанной в §6.2. Следовательно, контроль доступа к членам (поля, методы, типы) применяется явным образом выражениями доступа к полям, выражениями вызова методов и выражениями квалифицированного создания экземпляра класса. (Обратите внимание, что доступ к полю также может быть обозначен квалифицированным именем, встречающимся как постфиксное выражение.)
Обратите внимание, что квалифицированные имена, выражения доступа к полям, выражения вызова методов и выражения квалифицированного создания экземпляра класса синтаксически похожи в том, что появляется маркер «.», предваряемый указанием пакета, типа или выражения, имеющего тип, и за которым следует Идентификатор, который называет член пакета или типа. (Маркер new стоит между . и Идентификатором в выражении квалифицированного создания экземпляра класса.)
Многие операторы и выражения позволяют использовать типы вместо имён типов. Например, объявление класса может использовать параметризованный тип (§4.5) для обозначения суперкласса. Поскольку параметризованный тип не является квалифицированным именем типа, необходимо, чтобы объявление класса явно выполняло контроль доступа к обозначенному суперклассу. Следовательно, большинство операторов и выражений, которые предоставляют контексты в §6.5.1 для классификации ИмениТипа, также должны выполнять свои проверки доступа.
Помимо доступа к членам пакета или ссылочного типа, существует вопрос доступа к конструкторам ссылочного типа. Контроль доступа должен проверяться при явном или неявном вызове конструктора. Следовательно, контроль доступа проверяется оператором явного вызова конструктора (§8.8.7.1) и выражением создания экземпляра класса (§15.9.3). Эти «ручные» проверки необходимы, потому что §6.5.1 игнорирует явные вызовы конструкторов (потому что они ссылаются на имена конструкторов косвенно) и не осведомлен о различии между типом класса, обозначаемым неквалифицированным выражением создания экземпляра класса, и конструктором этого типа класса. Кроме того, конструкторы не имеют квалифицированных имён, поэтому мы не можем полагаться на то, что контроль доступа проверяется во время классификации квалифицированных имён типов.
Доступность влияет на наследование членов класса (§8.2), включая скрытие и переопределение методов (§8.4.8.1).
-
Пакет всегда доступен.
-
Если тип класса или интерфейса объявлен
public, то к нему может получить доступ любой код, при условии, что единица компиляции (§7.3) в которой он объявлен, доступна.Если тип верхнего уровня класса или интерфейса не объявлен
public, то к нему можно получить доступ только изнутри пакета, в котором он объявлен. -
Тип массива доступен тогда и только тогда, когда доступен его элементный тип.
-
Член (класс, интерфейс, поле или метод) типа ссылки (класс, интерфейс или массив) или конструктор типа класса доступен только в том случае, если тип доступен и член или конструктор объявлен так, чтобы разрешить доступ:
-
Если член или конструктор объявлен
public, то доступ разрешен.Все члены интерфейсов неявным образом
public. -
В противном случае, если член или конструктор объявлен
protected, то доступ разрешен только если выполняется одно из следующих условий:-
Доступ к члену или конструктору происходит изнутри пакета, содержащего класс, в котором объявлен защищённый член или конструктор.
-
Доступ корректен, как описано в §6.6.2.
-
-
В противном случае, если член или конструктор объявлен
private, то доступ разрешен тогда и только тогда, когда он происходит внутри тела класса верхнего уровня (§7.6), который включает объявление члена или конструктора. -
В противном случае, мы говорим о доступе по умолчанию, который разрешен только в том случае, если доступ происходит изнутри пакета, в котором объявлен тип.
-
Пример 6.6-1. Управление доступом
Рассмотрим две единицы компиляции:
package points;
class PointVec { Point[] vec; }
и:
package points;
public class Point {
protected int x, y;
public void move(int dx, int dy) { x += dx; y += dy; }
public int getX() { return x; }
public int getY() { return y; }
}
которые объявляют два типа классов в пакете points:
-
Тип класса
PointVecнеpublicи не является частью интерфейсаpublicпакетаpoints, а может использоваться только другими классами в пакете. -
Тип класса
Pointобъявленpublicи доступен для других пакетов. Он является частью интерфейсаpublicпакетаpoints. -
Методы
move,getXиgetYклассаPointобъявленыpublicи поэтому доступны любому коду, использующему объект типаPoint. -
Поля
xиyобъявленыprotectedи доступны за пределами пакетаpointsтолько в подклассах классаPoint, и только когда они являются полями объектов, реализуемыми кодом, который к ним обращается.См. §6.6.2 для примера того, как модификатор доступа
protectedограничивает доступ.
Пример 6.6-2. Доступ к полям, методам и конструкторам типа public
Член или конструктор класса public доступен во всём пакете, где он объявлен, и из любого другого пакета, при условии, что пакет, в котором он объявлен, доступен (§7.4.3). Например, в единице компиляции:
package points;
public class Point {
int x, y;
public void move(int dx, int dy) {
x += dx; y += dy;
moves++;
}
public static int moves = 0;
}
класс public класса Point имеет в качестве public членов метод move и поле moves. Эти public члены доступны любому другому пакету, у которого есть доступ к пакету points. Поля x и y не public и поэтому доступны только изнутри пакета points.
Пример 6.6-3. Доступ к классам public и не-public
Если у класса отсутствует модификатор public, доступ к объявлению класса ограничен пакетом, в котором он объявлен (§6.6). В примере:
package points;
public class Point {
public int x, y;
public void move(int dx, int dy) { x += dx; y += dy; }
}
class PointList {
Point next, prev;
}
в единице компиляции объявлены два класса. Класс Point доступен за пределами пакета points, а класс PointList доступен только внутри пакета. Таким образом, единица компиляции в другом пакете может получить доступ к классу points.Point, используя его полное имя:
package pointsUser;
class Test1 {
public static void main(String[] args) {
points.Point p = new points.Point();
System.out.println(p.x + " " + p.y);
}
}
или с помощью объявления импорта одного типа (§7.5.1), упоминающего полное имя, чтобы затем использовать простое имя:
package pointsUser;
import points.Point;
class Test2 {
public static void main(String[] args) {
Point p = new Point();
System.out.println(p.x + " " + p.y);
}
}
Однако, эта единица компиляции не может использовать или импортировать points.PointList, который не объявлен public и поэтому недоступен за пределами пакета points.
Пример 6.6-4. Доступ к полям, методам и конструкторам с доступом по умолчанию
Если ни один из модификаторов доступа public, protected или private не указан, член или конструктор класса доступен во всём пакете, который содержит объявление класса, в котором объявлен член класса, но член или конструктор недоступен ни в каком другом пакете.
Если у класса public есть метод или конструктор с доступом по умолчанию, то этот метод или конструктор недоступен и не наследуется подклассом, объявленным за пределами этого пакета.
Например, если у нас есть:
package points;
public class Point {
public int x, y;
void move(int dx, int dy) { x += dx; y += dy; }
public void moveAlso(int dx, int dy) { move(dx, dy); }
}
то подкласс в другом пакете может объявить несвязанный метод move с той же сигнатурой (§8.4.2) и возвращаемым типом. Поскольку исходный метод move недоступен из пакета morepoints, super не может быть использован:
package morepoints;
public class PlusPoint extends points.Point {
public void move(int dx, int dy) {
super.move(dx, dy); // compile-time error
moveAlso(dx, dy);
}
}
Так как move метода Point не переопределён move в PlusPoint, метод moveAlso в Point никогда не вызывает метод move в PlusPoint. Таким образом, если вы удалите вызов super.move из PlusPoint и выполните программу тестирования:
import points.Point;
import morepoints.PlusPoint;
class Test {
public static void main(String[] args) {
PlusPoint pp = new PlusPoint();
pp.move(1, 1);
}
}
она завершается нормально. Если move метода Point был бы переопределён move в PlusPoint, то эта программа рекурсивно выполнялась бы до возникновения StackOverflowError.
Пример 6.6-5. Доступ к полям, методам и конструкторам типа private
Член или конструктор класса private доступен только внутри тела класса верхнего уровня (§7.6), который включает объявление члена или конструктора. Он не наследуется подклассами. В примере:
class Point {
Point() { setMasterID(); }
int x, y;
private int ID;
private static int masterID = 0;
private void setMasterID() { ID = masterID++; }
}
приватные члены ID, masterID и setMasterID могут быть использованы только внутри тела класса Point. К ним нельзя получить доступ с помощью квалифицированных имён, выражений доступа к полям или выражений вызова методов вне тела объявления класса Point.
См. §8.8.8 для примера использования конструктора private.
К члену или конструктору объекта извне пакета, в котором он объявлен, можно получить доступ только коду, отвечающему за реализацию этого объекта.
Пусть C — класс, в котором объявлен защищённый член. Доступ разрешён только внутри тела подкласса S класса C.
Кроме того, если Id обозначает поле или метод экземпляра, то:
-
Если доступ осуществляется с помощью квалифицированного имени
Q.Id, гдеQ— ИмяВыражения, то доступ разрешён тогда и только тогда, когда тип выраженияQ—Sили подклассS. -
Если доступ осуществляется с помощью выражения доступа к полю
E.Id, гдеE— выражение Основное, или с помощью выражения вызова методаE.Id(. . .), гдеE— выражение Основное, то доступ разрешён тогда и только тогда, когда типE—Sили подклассS.
Дополнительную информацию о доступе к защищённым членам можно найти в статье Проверка доступа к защищенным членам в виртуальной машине Java Алессандро Коглио в журнале Journal of Object Technology, октябрь 2005 года.
Пусть C — класс, в котором объявлен защищённый конструктор, и S — самый внутренний класс, в объявлении которого используется данный конструктор. Тогда:
-
Если доступ осуществляется с помощью вызова конструктора суперкласса
super(. . .)или квалифицированного вызова конструктора суперкласса видаE.super(. . .), гдеE— выражение Основное, то доступ разрешён. -
Если доступ осуществляется с помощью выражения создания экземпляра анонимного класса вида
new C(. . .){...}или квалифицированного выражения создания экземпляра класса вида E.new C(. . .){...}, где E — выражение Основное, то доступ разрешён. -
В противном случае, если доступ осуществляется с помощью простого выражения создания экземпляра класса вида
new C(. . .)или квалифицированного выражения создания экземпляра класса вида E.new C(. . .), где E — выражение Основное, то доступ запрещён.К защищённому конструктору можно получить доступ с помощью выражения создания экземпляра класса (которое не объявляет анонимный класс) только изнутри пакета, в котором он определён.
Пример 6.6.2-1. Доступ к защищённым полям, методам и конструкторам
Рассмотрим пример, где пакет points объявляет:
package points;
public class Point {
protected int x, y;
void warp(threePoint.Point3d a) {
if (a.z > 0) // compile-time error: cannot access a.z
a.delta(this);
}
}
а пакет threePoint объявляет:
package threePoint;
import points.Point;
public class Point3d extends Point {
protected int z;
public void delta(Point p) {
p.x += this.x; // compile-time error: cannot access p.x
p.y += this.y; // compile-time error: cannot access p.y
}
public void delta3d(Point3d q) {
q.x += this.x;
q.y += this.y;
q.z += this.z;
}
}
В методе delta возникает ошибка компиляции: он не может получить доступ к защищённым членам x и y своего параметра p, потому что, хотя Point3d (класс, в котором происходят ссылки на поля x и y) является подклассом Point (класс, в котором объявлены x и y), он не участвует в реализации Point (тип параметра p). Метод delta3d может получить доступ к защищённым членам своего параметра q, поскольку класс Point3d является подклассом Point и участвует в реализации Point3d.
Метод delta мог бы попытаться привести (§5.5, §15.16) свой параметр к типу Point3d, но это приведение не удалось бы, вызвав исключение, если бы класс p во время выполнения не был классом Point3d.
Ошибка компиляции также возникает в методе warp: он не может получить доступ к защищённому члену z своего параметра a, потому что, хотя класс Point (класс, в котором происходит ссылка на поле z) участвует в реализации Point3d (тип параметра a), он не является подклассом Point3d (класс, в котором объявлен z).
Каждый примитивный тип, именованный пакет, класс верхнего уровня и интерфейс верхнего уровня имеет полное имя:
-
Полное имя примитивного типа — это ключевое слово для данного примитивного типа, а именно
byte,short,char,int,long,float,doubleилиboolean. -
Полное имя именованного пакета, не являющегося подпакетом именованного пакета, — это его простое имя.
-
Полное имя именованного пакета, являющегося подпакетом другого именованного пакета, состоит из полного имени содержащего пакета, за которым следует "
.", а затем простое (членское) имя подпакета. -
Полное имя класса верхнего уровня или интерфейса верхнего уровня, объявленного в неопределенном пакете, — это простое имя класса или интерфейса.
-
Полное имя класса верхнего уровня или интерфейса верхнего уровня, объявленного в именованном пакете, состоит из полного имени пакета, за которым следует "
.", а затем простое имя класса или интерфейса.
Каждый вложенный класс, вложенный интерфейс и тип массива могут иметь полное имя:
-
Вложенный класс или вложенный интерфейс M другого класса или интерфейса C имеет полное имя тогда и только тогда, когда C имеет полное имя.
В этом случае полное имя M состоит из полного имени C, за которым следует "
.", а затем простое имя M. -
Тип массива имеет полное имя тогда и только тогда, когда его элементный тип имеет полное имя.
В этом случае полное имя типа массива состоит из полного имени компонентного типа массива, за которым следует "
[]".
Локальный класс не имеет полного имени.
Пример 6.7-1. Полные имена
-
Полное имя типа
long— "long". -
Полное имя пакета
java.lang— "java.lang", так как это подпакетlangпакетаjava. -
Полное имя класса
Object, определенного в пакетеjava.lang, — "java.lang.Object". -
Полное имя интерфейса
Enumeration, определенного в пакетеjava.util, — "java.util.Enumeration". -
Полное имя типа "массив
double" — "double[]". -
Полное имя типа "массив массивов массивов массивов
String" — "java.lang.String[][][][]".
В коде:
package points;
class Point { int x, y; }
class PointVec { Point[] vec; }
полное имя типа Point — "points.Point"; полное имя типа PointVec — "points.PointVec"; и полное имя типа поля vec класса PointVec — "points.Point[]".
Каждый примитивный тип, именованный пакет, класс верхнего уровня и интерфейс верхнего уровня имеет каноническое имя:
-
Для каждого примитивного типа, именованного пакета, класса верхнего уровня и интерфейса верхнего уровня каноническое имя такое же, как полное имя.
Каждый вложенный класс, вложенный интерфейс и тип массива могут иметь каноническое имя:
-
Вложенный класс или вложенный интерфейс M, объявленный в другом классе C, имеет каноническое имя тогда и только тогда, когда C имеет каноническое имя.
В этом случае каноническое имя M состоит из канонического имени C, за которым следует "
.", а затем простое имя M. -
Тип массива имеет каноническое имя тогда и только тогда, когда его компонентный тип имеет каноническое имя.
В этом случае каноническое имя типа массива состоит из канонического имени компонентного типа массива, за которым следует "
[]".
Локальный класс не имеет канонического имени.
Пример 6.7-2. Полные имена и Канонические Имена
Разница между полным именем и каноническим именем может быть продемонстрирована на примере кода:
package p;
class O1 { class I {} }
class O2 extends O1 {}
И p.O1.I, и p.O2.I являются полными именами, обозначающими вложенный класс I, но только p.O1.I является его каноническим именем.
© Oracle and/or its affiliates. All rights reserved.
Licensed under the Oracle Technology Network License Agreement.