Глава 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), в выражении ссылки на метод (§15.13) или в квалифицированном выражении создания экземпляра класса (§15.9). В отсутствие модификатора доступа большинство объявлений имеют доступ к пакету, позволяя доступ в любом месте пакета, содержащего объявление; другие варианты — это public, protected и private.
Полные и канонические имена (§6.7) также обсуждаются в этой главе.
END_OF_DOCUMENT_MARKERОбъявление вводит сущность в программу и включает идентификатор (§3.8), который может использоваться в имени для ссылки на эту сущность. Идентификатор ограничен, чтобы избежать определенных контекстных ключевых слов, когда вводимая сущность является классом, интерфейсом или параметром типа.
Объявленная сущность является одной из следующих:
-
Модуль, объявленный в объявлении
module(§7.7) -
Пакет, объявленный в объявлении
package(§7.4) -
Импортированный класс или интерфейс, объявленный в объявлении импорта типа или объявлении импорта типа по требованию (§7.5.1, §7.5.2)
-
Импортированный член
static, объявленный в объявлении одноэлементного статического импорта или объявлении статического импорта по требованию (§7.5.3, §7.5.4) -
Класс, объявленный нормальным объявлением класса (§8.1), объявлением перечисления (§8.9) или объявлением записи (§8.10)
-
Интерфейс, объявленный нормальным объявлением интерфейса (§9.1) или объявлением аннотационного интерфейса (§9.6).
-
Параметр типа, объявленный как часть объявления обобщенного класса, интерфейса, метода или конструктора (§8.1.2, §9.1.2, §8.4.4, §8.8.4)
-
Член типа ссылки (§8.2, §9.2, §8.9.3, §9.6, §10.7), один из следующих:
-
Константа перечисления (§8.9.1)
-
Компонент записи (§8.10.3)
-
Формальный параметр, один из следующих:
-
Параметр исключения обработчика исключений, объявленный в блоке
catchоператораtry(§14.20) -
Локальная переменная, одна из следующих:
-
Вложенный класс или интерфейс (§14.3), один из следующих:
-
Вложенный класс, объявленный нормальным объявлением класса
-
Вложенный класс, объявленный объявлением перечисления
-
Вложенный класс, объявленный объявлением записи
-
Вложенный интерфейс, объявленный нормальным объявлением интерфейса
-
Конструкторы (§8.8, §8.10.4) также вводятся объявлениями, но используют имя класса, в котором они объявлены, а не вводят новое имя.
Объявление обобщенного класса или интерфейса (class C<T> ... или interface C<T> ...) вводит как класс с именем C, так и семейство типов: необработанный тип C, параметризованный тип C<Foo>, параметризованный тип C<Bar> и т. д.
Когда ссылка на C встречается там, где обобщенность не важна, указано ниже как один из необобщенных контекстов, ссылка на C обозначает класс или интерфейс C. В других контекстах ссылка на C обозначает тип или часть типа, введенного C.
15 необобщенных контекстов следующие:
-
В директиве
usesилиprovidesв объявлении модуля (§7.7.1) -
В объявлении импорта одного типа (§7.5.1)
-
Слева от
.в объявлении импорта одного статического элемента (§7.5.3) -
Слева от
.в объявлении импорта всех статических элементов (§7.5.4) -
В разделе
permitsобъявления класса или интерфейсаsealed(§8.1.6, §9.1.4). -
Слева от
(в объявлении конструктора (§8.8) -
После знака
@в аннотации (§9.7) -
Слева от
.classв литерале класса (§15.8.2) -
Слева от
.thisв квалифицированном выраженииthis(§15.8.4) -
Слева от
.superв квалифицированном выражении доступа к полю суперкласса (§15.11.2) -
Слева от
.Идентификатор или.super.Идентификатор в квалифицированном выражении вызова метода (§15.12) -
Слева от
.super::в выражении ссылки на метод (§15.13) -
В квалифицированном имени выражения в постфиксном выражении или операторе
tryс ресурсами (§15.14.1, §14.20.3) -
В разделе
throwsметода или конструктора (§8.4.6, §8.8.5, §9.4) -
В объявлении параметра исключения (§14.20)
Первые двенадцать контекстов без дженериков соответствуют первым двенадцати синтаксическим контекстам для TypeName в §6.5.1. Тринадцатый контекст без дженериков — это контекст, где квалифицированное ExpressionName, такое как C.x, может включать TypeName C для обозначения доступа к статическому члену. Распространённое использование TypeName в этих тринадцати контекстах важно: оно указывает, что эти контексты включают менее первостепенное использование типа. В отличие от этого, четырнадцатый и пятнадцатый контексты без дженериков используют ClassType, указывая, что throws и catch разделы используют типы первостепенным образом, в соответствии, например, с объявлением полей. Описание этих двух контекстов как контекстов без дженериков обусловлено тем, что тип исключения не может быть параметризован (§8.1.2).
Обратите внимание, что производство ClassType допускает аннотации, поэтому можно аннотировать использование типа в throws или catch разделе, тогда как производство TypeName запрещает аннотации, поэтому нельзя аннотировать имя типа, например, в объявлении импорта одного типа.
Правила именования
Библиотеки классов Java SE Platform пытаются использовать, когда это возможно, имена, выбранные в соответствии с нижеприведенными правилами. Эти правила помогают сделать код более читаемым и избежать определённых типов конфликтов имён.
Мы рекомендуем использовать эти правила во всех программах, написанных на языке программирования Java. Однако этих правил не следует слепо придерживаться, если существующая традиционная практика диктует иное. Например, методы sin и cos класса java.lang.Math имеют математически традиционные имена, даже если эти имена методов нарушают рекомендуемые правила, потому что они короткие и не являются глаголами.
Имена пакетов и модулей
Программисты должны принимать меры, чтобы избежать возможности наличия двух опубликованных пакетов с одинаковым именем, выбирая уникальные имена пакетов для пакетов, которые широко распространены. Это позволяет легко и автоматически устанавливать и каталогизировать пакеты. В этом разделе описаны рекомендуемые правила для генерации таких уникальных имён пакетов. Реализации Java SE Platform рекомендуют обеспечивать автоматическую поддержку преобразования набора пакетов из локальных и обычных имён пакетов в формат уникальных имён, описанный здесь.
Если уникальные имена пакетов не используются, могут возникнуть конфликты имён пакетов далеко от места создания любого из конфликтующих пакетов. Это может создать ситуацию, которую сложно или невозможно разрешить пользователю или программисту. Классы ClassLoader и ModuleLayer могут использоваться для изоляции пакетов с одинаковым именем друг от друга в тех случаях, когда взаимодействие пакетов ограничено, но не таким образом, чтобы это было прозрачно для обычной программы.
Для формирования уникального имени пакета необходимо сначала иметь (или принадлежать организации, которая имеет) доменное имя в Интернете, например, oracle.com. Затем это имя необходимо развернуть по компонентам, чтобы в этом примере получить com.oracle, и использовать его в качестве префикса для имён пакетов, используя соглашение, разработанное внутри вашей организации для дальнейшего управления именами пакетов. Такое соглашение может предусматривать, что определённые компоненты имени пакета являются разделом, отделом, проектом, машиной или именем пользователя.
Пример 6.1-1. Уникальные имена пакетов
com.nighthacks.scrabble.dictionary org.openjdk.compiler.source.tree net.jcip.annotations edu.cmu.cs.bovik.cheese gov.whitehouse.socks.mousefinder
Первый компонент уникального имени пакета всегда записывается строчными латинскими буквами ASCII и должен быть одним из доменных имён верхнего уровня, таких как com, edu, gov, mil, net или org, или одним из двухбуквенных английских кодов, определяющих страны, как указано в Стандарте ISO 3166.
В некоторых случаях доменное имя может не быть допустимым именем пакета. Вот некоторые рекомендуемые соглашения для решения таких ситуаций:
-
Если доменное имя содержит дефис или любой другой специальный символ, не разрешённый в идентификаторе (§3.8), замените его на нижнее подчеркивание.
-
Если какой-либо из получившихся компонентов имени пакета является ключевым словом (§3.9), добавьте к нему нижнее подчеркивание.
-
Если какой-либо из получившихся компонентов имени пакета начинается с цифры или любого другого символа, который не разрешён в качестве первого символа идентификатора, добавьте нижнее подчеркивание перед этим компонентом.
Имя модуля должно соответствовать имени его основного экспортируемого пакета. Если у модуля нет такого пакета или по причинам совместимости с предыдущими версиями у него должно быть имя, которое не соответствует одному из его экспортируемых пакетов, то его имя всё равно должно начинаться с обращённой формы доменного имени в Интернете, с которым связан его автор.
Пример 6.1-2. Уникальные имена модулей
com.nighthacks.scrabble org.openjdk.compiler net.jcip.annotations
Первый компонент имени пакета или модуля не должен быть идентификатором java. Имена пакетов и модулей, начинающиеся с идентификатора java, зарезервированы для пакетов и модулей Java SE Platform.
Имя пакета или модуля не должно подразумевать, где пакет или модуль хранятся в Интернете. Например, пакет с именем edu.cmu.cs.bovik.cheese не обязательно доступен по адресу cmu.edu или cs.cmu.edu или bovik.cs.cmu.edu. Предлагаемое соглашение для генерации уникальных имён пакетов и модулей просто позволяет использовать соглашение об именовании пакетов и модулей в существующей широко известной системе регистрации уникальных имён вместо создания отдельной системы регистрации для имён пакетов и модулей.
Имена классов и интерфейсов
Имена классов должны быть описательными существительными или словосочетаниями, не слишком длинными, с использованием смешанного регистра, где первая буква каждого слова заглавная.
Пример 6.1-3. Описательные имена классов
ClassLoaderSecurityManagerThreadDictionary BufferedInputStream
Аналогично, имена интерфейсов должны быть короткими и описательными, не чрезмерно длинными, с использованием смешанного регистра, при этом первая буква каждого слова должна быть заглавной. Имя может быть описательным существительным или существительным во фразе, что уместно, когда интерфейс используется как абстрактный суперкласс, например, интерфейсы java.io.DataInput и java.io.DataOutput; или это может быть прилагательное, описывающее поведение, как для интерфейсов Runnable и Cloneable.
Имена переменных типа
Имена переменных типа должны быть емкими (если возможно, одной буквой) и выразительными, и не должны включать строчные буквы. Это упрощает различение параметров типа от обычных классов и интерфейсов.
Классы и интерфейсы контейнеров должны использовать имя E для типа их элементов. Карты должны использовать K для типа своих ключей и V для типа своих значений. Имя X должно использоваться для произвольных типов исключений. Мы используем T для типа, когда нет более специфической информации о типе для его различения. (Это часто встречается в методах дженериков.)
Если есть несколько параметров типа, обозначающих произвольные типы, следует использовать буквы, близкие к T в алфавите, такие как S. В качестве альтернативы допустимо использовать числовые индексы (например, T1, T2) для различения различных переменных типа. В таких случаях все переменные с одинаковым префиксом должны иметь индекс.
Если метод дженерика появляется внутри дженерик-класса, рекомендуется избегать использования одинаковых имён для параметров типа метода и класса, чтобы избежать путаницы. То же самое относится к вложенным дженерик-классам.
Пример 6.1-4. Стандартные имена переменных типа
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), которые ссылаются на метки операторов.Идентификаторы, используемые в помеченных операторах и связанных с ними операторах
breakиcontinue, полностью отделены от тех, которые используются в объявлениях. -
В выражениях доступа к полям (§15.11), где идентификатор стоит после токена "
." для указания члена объекта, обозначенного выражением перед токеном ".", или объекта, обозначаемогоsuperили TypeName.superперед токеном ".". -
В некоторых выражениях вызова метода (§15.12), где идентификатор находится после токена "
." и перед токеном "(" для указания метода, который должен быть вызван для объекта, обозначенного выражением перед токеном ".", или типа, обозначенного TypeName перед токеном ".", или объекта, обозначенногоsuperили TypeName.superперед токеном ".". -
В некоторых выражениях ссылки на метод (§15.13), где идентификатор находится после токена "
::" для указания метода объекта, обозначенного выражением перед токеном "::", или типа, обозначенного TypeName перед токеном "::", или объекта, обозначаемогоsuperили TypeName.superперед токеном "::". -
В выражениях создания экземпляров класса с квалификацией (§15.9), где идентификатор находится справа от токена
newдля обозначения типа, являющегося членом времени компиляции типа выражения, предшествующего токенуnew. -
В парах имя-значение аннотаций (§9.7.1), для обозначения элемента соответствующего интерфейса аннотации.
В этой программе:
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.length является именем, потому что args.length — квалифицированное имя (§6.5.6.2), а не выражение доступа к полю (§15.11). Выражение доступа к полю, а также выражение вызова метода, выражение ссылки на метод и выражение создания экземпляра класса с квалификацией используют идентификатор, а не имя для обозначения интересующего члена. Таким образом, вхождение length в args[0].length() — не имя, а идентификатор, используемый в выражении вызова метода.
Можно задаться вопросом, почему в этих выражениях используется идентификатор, а не простое имя, которое, в конце концов, просто идентификатор. Причина в том, что простое имя выражения определяется в терминах лексической среды; то есть, простое имя выражения должно находиться в области видимости объявления переменной (§6.5.6.1). С другой стороны, доступ к полям, квалифицированный вызов метода, ссылки на методы и создание экземпляров класса с квалификацией относятся к членам, имена которых не находятся в лексической среде. По определению, такие имена связываются только в контексте, предоставляемом Primary выражения доступа к полю, выражения вызова метода, выражения ссылки на метод или выражения создания экземпляра класса; или Qualifier выражения доступа к полю, выражения вызова метода или выражения ссылки на метод; и так далее. Поэтому мы обозначаем таких членов идентификаторами, а не простыми именами.
Чтобы еще больше усложнить ситуацию, выражение доступа к полю — не единственный способ обозначить поле объекта. По причинам разбора, квалифицированное имя используется для обозначения поля в области видимости переменной. (Сама переменная обозначается простым именем, упомянутым выше.) Необходимо, чтобы контроль доступа (§6.6) применялся к обоим обозначениям поля.
Область действия объявления — это область программы, в пределах которой к сущности, объявленной этим объявлением, можно обратиться с помощью простого имени, при условии, что она не затеняется (§6.4.1).
Считается, что объявление находится в области действия в определенной точке программы тогда и только тогда, когда область действия объявления включает эту точку.
Область действия объявления наблюдаемого верхнего уровня пакета (§7.4.3) охватывает все наблюдаемые единицы компиляции, связанные с модулями, для которых пакет имеет уникальную видимость (§7.4.3).
Объявление пакета, который не наблюдаем, никогда не находится в области действия.
Объявление подпакета никогда не находится в области действия.
Пакет java всегда находится в области действия.
Область действия класса или интерфейса, импортированного объявлением импорта одного типа (§7.5.1) или объявлением импорта по требованию типа (§7.5.2), — это объявление модуля (§7.7) и все объявления классов и интерфейсов (§8.1, §9.1) в единице компиляции, в которой появляется объявление import, а также все аннотации к объявлениям модуля или пакета этой единицы компиляции.
Область действия члена, импортированного объявлением импорта одного статического члена (§7.5.3) или объявлением импорта статических членов по требованию (§7.5.4), — это объявление модуля и все объявления классов и интерфейсов в единице компиляции, в которой находится объявление import, а также все аннотации к объявлениям модуля или пакета этой единицы компиляции.
Область действия верхнего уровня класса или интерфейса (§7.6) — это все объявления классов и интерфейсов в пакете, в котором объявлен верхнеуровневый класс или интерфейс.
Область действия объявления члена m, объявленного в классе или интерфейсе C или унаследованного от него (§8.2, §9.2) — это весь текст C, включая объявления вложенных классов или интерфейсов.
Область действия формального параметра метода (§8.4.1), конструктора (§8.8.1) или лямбда-выражения (§15.27) — это весь текст метода, конструктора или лямбда-выражения.
Область действия параметра типа класса (§8.1.2) — это раздел параметров типа объявления класса, раздел параметров типа любого суперкласса или суперинтерфейса объявления класса и тело класса. Если класс является записывающим классом (§8.10), то область действия параметра типа дополнительно включает заголовок объявления записи (§8.10.1).
Область действия параметра типа интерфейса (§9.1.2) — это раздел параметров типа объявления интерфейса, раздел параметров типа любого суперинтерфейса объявления интерфейса и тело интерфейса.
Область действия параметра типа метода (§8.4.4) — это все объявление метода, включая раздел параметров типа, но не включая модификаторы метода.
Область действия параметра типа конструктора (§8.8.4) — это все объявление конструктора, включая раздел параметров типа, но не включая модификаторы конструктора.
Область действия объявления вложенного класса или интерфейса, непосредственно заключенного в блок (§14.2), — это остальная часть непосредственно содержащего блока, включая само объявление вложенного класса или интерфейса.
Область действия объявления вложенного класса или интерфейса, непосредственно заключенного в группу инструкций switch (§14.11), — это остальная часть непосредственно содержащей группы инструкций switch, включая само объявление вложенного класса или интерфейса.
Область действия локальной переменной, объявленной в блоке с помощью оператора объявления локальной переменной (§14.4.2), — это остальная часть блока, начиная с инициализатора объявления и включая все последующие объявления в операторе объявления локальной переменной.
Область действия локальной переменной, объявленной в части ForInit оператора цикла basic for (§14.14.1) включает в себя:
-
Собственный инициализатор
-
Любые последующие объявления справа в части ForInit оператора цикла
-
Части Expression и ForUpdate оператора цикла
-
Содержащуюся инструкцию Statement
Область действия локальной переменной, объявленной в заголовке оператора цикла enhanced for (§14.14.2), — это содержащаяся инструкция Statement.
Область действия локальной переменной, объявленной в спецификации ресурсов оператора try-with-resources (§14.20.3) — это область от объявления вправо до остальной части спецификации ресурсов и всего блока try, связанного с оператором try-with-resources.
Перевод оператора try-with-resources подразумевает вышеупомянутое правило.
Область действия параметра обработчика исключения, объявленного в блоке catch оператора try (§14.20) — это весь блок, связанный с catch.
Пример 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. Область действия объявления локальных переменных
Следующая программа приводит к ошибке компиляции, потому что инициализация локальной переменной x находится в области действия объявления локальной переменной x, но локальная переменная x еще не имеет значения и не может быть использована. Поле x имеет значение 0 (присвоено при инициализации Test1), но является «ложным следом», так как оно затеняется (§6.4.1) локальной переменной x.
class Test1 {
static int x;
public static void main(String[] args) {
int x = x;
}
}
Следующая программа компилируется:
class Test2 {
static int x;
public static void main(String[] args) {
int x = (x=2)*2;
System.out.println(x);
}
}
потому что локальной переменной x определенно присваивается значение (§16 (Definite Assignment)) перед её использованием. Она выводит:
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.30.2). Она определяется путём рассмотрения точек программы, где переменная шаблона определённо сопоставляется в области, начинающейся с шаблона, который объявляет переменную шаблона.
Остальная часть данного раздела посвящена точному толкованию слов "определённо сопоставляется". Анализ учитывает структуру операторов и выражений, с особым вниманием к операторам логических выражений и определённым формам операторов.
Видно, что область действия объявления переменной шаблона — это понятие, зависящее от потока, аналогичное определенному присваиванию (§16 (Определённое присваивание)). Правила, определённые в остальной части этого раздела, умышленно имеют сходную форму с правилами определённого присваивания.
Анализ опирается на технический термин «введённый», который имеет следующий вид:
-
переменная шаблона вводится выражением при истинном значении
-
переменная шаблона вводится выражением при ложном значении
-
переменная шаблона вводится оператором
Простейший пример — переменная шаблона s вводится выражением a instanceof String s при истинном значении. Другими словами, если значение выражения равно true, то сопоставление шаблонов должно было быть успешным, а значит, переменная шаблона должна была получить значение.
В противоположность этому, переменная шаблона t вводится выражением !(b instanceof Integer t) при ложном значении. Это связано с тем, что сопоставление шаблонов могло быть успешным только в том случае, если значение выражения равно false.
Только определённые виды логических выражений участвуют во введении переменных шаблонов и определении мест, где эти переменные гарантированно сопоставлены. Если выражение не является выражением с оператором «и», выражением с оператором «или», выражением логического отрицания, условным выражением, выражением instanceof, выражением switch или выражением в скобках, то правила области действия не применяются.
Следующие правила применяются к выражению с оператором «и» a && b (§15.23):
-
Переменная шаблона, введённая с помощью
aпри истинном значении, гарантированно сопоставляется вb.Ошибка компиляции, если любая переменная шаблона, введённая с помощью
aпри истинном значении, уже находится в области действия вb. -
Переменная шаблона вводится с помощью
a&&bпри истинном значении тогда и только тогда, когда либо (i) она вводится с помощьюaпри истинном значении, либо (ii) она вводится с помощьюbпри истинном значении.
Следует отметить, что нет правила для введения переменной шаблона с помощью a && b при ложном значении. Это связано с тем, что в момент компиляции невозможно определить, какой операнд примет значение false.
Ошибка компиляции, если выполняются какие-либо из следующих условий:
-
Переменная шаблона вводится как (i) с помощью
aпри истинном значении, так и (ii) с помощьюbпри истинном значении. -
Переменная шаблона вводится как (i) с помощью
aпри ложном значении, так и (ii) с помощьюbпри ложном значении.
Эти два случая ошибок исключают возможность объявления переменной шаблона с одинаковым именем для обоих операндов оператора &&. Например, рассмотрим проблемное выражение (a instanceof String s) && (b
instanceof String s). Первый случай ошибки покрывает ситуацию, когда всё выражение вычисляет значение true, где (если бы код был корректным) потребовалось бы инициализировать две объявления переменной шаблона s, учитывая, что как левый, так и правый операнды вычислили значение true. Поскольку нет возможности отличить две переменные с именем s в остальной части программы, всё выражение считается ошибочным. Второй случай ошибки покрывает противоположную ситуацию, когда всё выражение вычисляет значение false.
Следующие правила применяются к выражению с оператором «или» a || b (§15.24):
-
Переменная шаблона, введённая с помощью
aпри ложном значении, гарантированно сопоставляется вb.Ошибка компиляции, если любая переменная шаблона, введённая с помощью
aпри ложном значении, уже находится в области действия вb. -
Переменная шаблона вводится с помощью
a||bпри ложном значении тогда и только тогда, когда либо (i) она вводится с помощьюaпри ложном значении, либо (ii) она вводится с помощьюbпри ложном значении.
Следует отметить, что нет правила для введения переменной шаблона с помощью a || b при истинном значении. Это связано с тем, что в момент компиляции невозможно определить, какой операнд примет значение true.
Ошибка компиляции, если выполняются какие-либо из следующих условий:
-
Переменная шаблона вводится как (i) с помощью
aпри истинном значении, так и (ii) с помощьюbпри истинном значении. -
Переменная шаблона вводится как (i) с помощью
aпри ложном значении, так и (ii) с помощьюbпри ложном значении.
Эти два случая ошибок исключают возможность объявления переменной шаблона с одинаковым именем для обоих операндов оператора ||. Например, рассмотрим проблемное выражение (a instanceof String s) || (b
instanceof String s). Первый случай ошибки покрывает ситуацию, когда всё выражение вычисляет значение true, где (если бы код был корректным) была бы инициализирована ровно одна декларация переменной шаблона s в зависимости от того, какой операнд вычислил значение true. Поскольку в момент компиляции невозможно определить, какой операнд вычислит значение true, а следовательно, какая декларация s будет инициализирована, всё выражение считается ошибочным. Второй случай ошибки покрывает противоположную ситуацию, когда всё выражение вычисляет значение false.
Следующие правила применяются к выражению логического отрицания !a (§15.15.6):
-
Переменная шаблона вводится с помощью
!aпри истинном значении тогда и только тогда, когда она вводится с помощьюaпри ложном значении. -
Переменная шаблона вводится с помощью
!aпри ложном значении тогда и только тогда, когда она вводится с помощьюaпри истинном значении.
Следующие правила применяются к условному выражению a ? b : c (§15.25):
-
Переменная шаблона, введённая с помощью
aпри истинном значении, гарантированно сопоставляется вb.Ошибка компиляции, если любая переменная шаблона, введённая с помощью
aпри истинном значении, уже находится в области действия вb. -
Переменная шаблона, введённая с помощью
aпри ложном значении, гарантированно сопоставляется вc.Ошибка компиляции, если любая переменная шаблона, введённая с помощью
aпри ложном значении, уже находится в области действия вc.
Следует отметить, что нет правил для введения переменной шаблона с помощью a ? b : c при истинном или ложном значении. Это связано с тем, что в момент компиляции невозможно определить, вычислит ли операнд a значение true.
Ошибка компиляции, если выполняются какие-либо из следующих условий:
-
Переменная шаблона вводится как (i) с помощью
aпри истинном значении, так и (ii) с помощьюcпри истинном значении. -
Переменная шаблона вводится как (i) с помощью
aпри истинном значении, так и (ii) с помощьюcпри ложном значении. -
Переменная шаблона вводится как (i) с помощью
aпри ложном значении, так и (ii) с помощьюbпри истинном значении. -
Переменная шаблона вводится как (i) с помощью
aпри ложном значении, так и (ii) с помощьюbпри ложном значении. -
Переменная шаблона вводится как (i) с помощью
bпри истинном значении, так и (ii) с помощьюcпри истинном значении. -
Переменная шаблона вводится как (i) с помощью
bпри ложном значении, так и (ii) с помощьюcпри ложном значении.
Эти случаи ошибок аналогичны аналогичным случаям ошибок для операторов && и ||. Они исключают запутанные случаи, когда могут возникнуть несколько деклараций одной и той же переменной шаблона в операндах оператора ? :.
Следующие правила применяются к выражению instanceof с операндом-шаблоном, a instanceof p (§15.20.2):
-
Переменная шаблона вводится
ainstanceofp, если истинно, что она объявлена шаблономp. Правила определения переменных шаблона, объявляемых шаблоном, приведены в §14.30.1.Если переменная шаблона, введённая
ainstanceofpпри истинности, уже находится в области видимости в выраженииinstanceof, это является ошибкой компиляции.
Переменная шаблона не может затенять другую локальную переменную (§6.4).
Следует отметить, что нет правила введения переменной шаблона a instanceof p при ложности.
Следующее правило применяется к выражению switch (§15.28):
-
Переменная шаблона, введённая оператором S в группе операторов со switch-меткой (§14.11.1), гарантированно соответствует всем операторам, следующим за S, если таковые имеются, в группе операторов со switch-меткой.
Следующие правила применяются к выражению в скобках (a) (§15.8.5):
-
Переменная шаблона вводится
(a), если истинно, что она вводитсяa, когда истинно. -
Переменная шаблона вводится
(a), если ложно, что она вводитсяa, когда ложно.
Лишь несколько типов операторов играют существенную роль в определении области действия переменных шаблонов.
Где оператор if, while, do или for содержит выражение, вводящее переменные шаблона, область действия этих переменных может в определённых обстоятельствах включать подоператоры оператора.
Например, в следующем операторе if-then-else область действия переменной шаблона s включает один подоператор, но не другой:
Object o = ...
if (o instanceof String s)
// s in scope for this substatement; no cast of o needed
System.out.println(s.replace('*', '_'));
else
// s not in scope for this substatement (hence, error)
System.out.println(s);
Также в определённых обстоятельствах переменная шаблона может быть введена самим оператором, а не выражением внутри оператора. Переменная шаблона, введённая оператором, находится в области действия следующих операторов в окружающем блоке.
Например, в следующем методе область действия переменной шаблона s включает тело метода после оператора if:
void test(Object o) {
if (!(o instanceof String s)) {
throw new IllegalArgumentException();
}
// This point is only reachable if the pattern match succeeded
// Thus, s is in scope for the rest of the block
...
System.out.println(s.repeat(5));
...
}
Следующее правило применяется к оператору блока S, содержащемуся в блоке (§14.2), который не является блоком switch (§14.11.1):
-
Переменная шаблона, введённая оператором S, определённо сопоставляется со всеми операторами блока, следующими за оператором S, если таковые имеются.
Следующие правила применяются к оператору if (e) S (§14.9.1):
-
Переменная шаблона, введённая
eпри истинном значении, определённо сопоставляется с оператором S.Если любая переменная шаблона, введённая
eпри истинном значении, уже находится в области действия оператора S, это ошибка компиляции. -
Переменная шаблона вводится
if(e)S тогда и только тогда, когда (i) она вводитсяeпри ложном значении и (ii) S не может завершиться нормально.Если любая переменная шаблона, введённая
if(e)S, уже находится в области действия оператораif, это ошибка компиляции.
Правило о введении переменной шаблона оператором if-then опирается на понятие «не может завершиться нормально» (§14.22), которое, в свою очередь, опирается на понятие константного выражения (§15.29). Это означает, что вычисление области действия переменной шаблона может потребовать определения, ссылается ли простое имя или квалифицированное имя в форме TypeName . Identifier на константную переменную. Поскольку переменные шаблонов никогда не могут ссылаться на константную переменную, цикличности нет.
Следующие правила применяются к оператору if (e) S else T (§14.9.2):
-
Переменная шаблона, введённая
eпри истинном значении, определённо сопоставляется с оператором S.Если любая переменная шаблона, введённая
eпри истинном значении, уже находится в области действия оператора S, это ошибка компиляции. -
Переменная шаблона, введённая
eпри ложном значении, определённо сопоставляется с оператором T.Если любая переменная шаблона, введённая
eпри ложном значении, уже находится в области действия оператора T, это ошибка компиляции. -
Переменная шаблона вводится
if(e)SelseT тогда и только тогда, когда выполняется одно из следующих условий:-
Она вводится
eпри истинном значении, S может завершиться нормально, а T не может завершиться нормально; или -
Она вводится
eпри ложном значении, S не может завершиться нормально, а T может завершиться нормально.
Если любая переменная шаблона, введённая
if(e)SelseT, уже находится в области действия оператораif, это ошибка компиляции. -
Эти правила отражают поточную природу области действия переменных шаблонов. Например, в следующем операторе:
if (e instanceof String s) {
counter += s.length();
} else {
System.out.println(e); // s not in scope
}
Переменная шаблона s вводится выражением instanceof и находится в области действия первого вложенного оператора (оператор присваивания в блоке then), но не находится в области действия второго вложенного оператора (оператор выражения в блоке else).
Кроме того, в сочетании с обработкой булевых выражений, область действия переменных шаблонов устойчива к изменениям кода, которые используют известные булевы логические эквивалентности. Например, предыдущий код можно переписать как:
if (!(e instanceof String s)) {
System.out.println(e); // s not in scope
} else {
counter += s.length();
}
Код можно даже переписать следующим образом, хотя двойное использование оператора ! не обязательно рекомендуется:
if (!!(e instanceof String s)) {
counter += s.length();
} else {
System.out.println(e); // s not in scope
}
Следующие правила применяются к оператору while (e) S (§14.12):
-
Переменная шаблона, введённая
eпри истинном значении, определённо сопоставляется с оператором S.Если любая переменная шаблона, введённая
eпри истинном значении, уже находится в области действия оператора S, это ошибка компиляции. -
Переменная шаблона вводится
while(e)S тогда и только тогда, когда (i) она вводитсяeпри ложном значении и (ii) S не содержит достижимого оператораbreak, целевой оператор break которого содержит S (§14.15).Если любая переменная шаблона, введённая
while(e)S, уже находится в области действия оператораwhile, это ошибка компиляции.
Следующие правила применяются к оператору do S while (e) (§14.13):
-
Переменная шаблона вводится
doSwhile(e)тогда и только тогда, когда (i) она вводитсяeпри ложном значении и (ii) S не содержит достижимого оператораbreak, целевой оператор break которого содержит S (§14.15).Если любая переменная шаблона, введённая
doSwhile(e), уже находится в области действия оператораdo, это ошибка компиляции.
Следующие правила применяются к оператору цикла for (§14.14.1):
-
Переменная образца, введённая выражением условия при истинности, однозначно сопоставляется как в части инкрементации, так и в содержащем операторе.
Если какая-либо переменная образца, введённая выражением условия при истинности, уже находится в области видимости в части инкрементации содержащего оператора, возникает ошибка компиляции.
-
Переменная образца вводится оператором цикла for при ложности условия, если (i) она вводится выражением условия при ложности и (ii) содержащий оператор S не содержит достижимый оператор break, целевой точкой которого является S (§14.15).
Если какая-либо переменная образца, введённая оператором цикла for, уже находится в области видимости в операторе
for, возникает ошибка компиляции.
Расширенный оператор цикла for (§14.14.2) определяется переводом в оператор цикла for, поэтому для него нет необходимости в особых правилах.
Следующее правило применяется к оператору switch (§14.11):
-
Переменная образца, введённая помеченным оператором S, содержащимся в группе операторов блока switch (§14.11.1), однозначно сопоставляется со всеми операторами, следующими за S (если таковые имеются) в группе операторов блока switch.
Следующее правило применяется к помеченному оператору (§14.7):
-
Переменная образца вводится помеченным оператором, если она вводится непосредственно содержащимся оператором Оператор.
Локальная переменная (§14.4), формальный параметр (§8.4.1, §8.8.1, §15.27.1), параметр исключения (§14.20), локальный класс или локальный интерфейс (§14.3) могут быть упомянуты только с помощью простого имени, а не с помощью квалифицированного имени (§6.2).
Некоторые объявления не допускаются в области видимости объявления локальной переменной, объявления формального параметра, объявления параметра исключения, объявления локального класса или локального интерфейса, поскольку невозможно будет отличить объявленные сущности, используя только простые имена.
Например, если имя формального параметра метода может быть повторно объявлено как имя локальной переменной в теле метода, то локальная переменная будет затемнять формальный параметр, и не будет способа обратиться к формальному параметру — нежелательный результат.
Ошибка компиляции, если имя формального параметра используется для объявления новой переменной в теле метода, конструктора или лямбда-выражения, за исключением случаев, когда новая переменная объявляется в объявлении класса или интерфейса, содержащемся в методе, конструкторе или лямбда-выражении.
Ошибка компиляции, если имя локальной переменной v используется для объявления новой переменной в области видимости v, за исключением случаев, когда новая переменная объявляется в объявлении класса или интерфейса, появившемся в области видимости v.
Ошибка компиляции, если имя параметра исключения используется для объявления новой переменной в блоке Block блока catch, за исключением случаев, когда новая переменная объявляется в объявлении класса или интерфейса, содержащемся в блоке Block блока catch.
Ошибка компиляции, если имя локального класса или интерфейса C используется для объявления нового локального класса или интерфейса в области видимости C, за исключением случаев, когда новый локальный класс или интерфейс объявляется в объявлении класса или интерфейса, появившемся в области видимости C.
Эти правила позволяют повторно объявлять переменную, локальный класс или локальный интерфейс во вложенных объявлениях класса или интерфейса, которые появляются в области видимости переменной, локального класса или локального интерфейса; такие вложенные объявления класса или интерфейса могут быть локальными объявлениями класса или интерфейса (§14.3) или объявлениями анонимного класса (§15.9.5). Таким образом, объявление формального параметра, локальной переменной, локального класса или локального интерфейса может быть затемнено в объявлении класса или интерфейса, вложенного в метод, конструктор или лямбда-выражение; а объявление параметра исключения может быть затемнено в объявлении класса или интерфейса, вложенном в блок Block блока catch.
Существуют два варианта проектирования для обработки конфликтов имён, созданных параметрами лямбда-выражений и другими переменными, объявленными в лямбда-выражениях. Первый — имитировать объявления классов: как локальные классы, лямбда-выражения вводят новый «уровень» для имён, и все имена переменных за пределами выражения могут быть повторно объявлены. Второй — «локальная» стратегия: как и блоки catch, циклы for и блоки, лямбда-выражения работают на том же «уровне», что и окружающий контекст, и локальные переменные за пределами выражения не могут быть затемнены. Вышеприведенные правила используют локальную стратегию; нет специального освобождения, позволяющего переменной, объявленной в лямбда-выражении, затемнять переменную, объявленную во внешнем методе.
Пример 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
Этот стиль также распространен при сопоставлении шаблонов, где повторяющиеся шаблоны часто используют одно и то же имя:
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
}
class Test4 {
static void test(Object a, Object b, Object c) {
if (a instanceof Point p) {
System.out.println("a is a point ("+p.x+","+p.y+")");
}
if (b instanceof Point p){
System.out.println("b is a point ("+p.x+","+p.y+")");
} else if (c instanceof Point p) {
System.out.println("c is a point ("+p.x+","+p.y+")");
}
}
public static void main(String[] args) {
Point p = new Point(2,3);
Point q = new Point(4,5);
Point r = new Point(6,7);
test(p, q, r);
}
}
Однако переменные шаблонов не допускаются для затемнения локальных переменных, в том числе и других переменных шаблонов, поэтому для следующей программы возникают две ошибки компиляции:
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
}
class Test5 {
static void test(Object a, Object b, Object c) {
if (a instanceof Point p) {
System.out.println("a is a point ("+p.x+","+p.y+")");
if (b instanceof Point p) { // compile-time error
System.out.println("b is a point ("+p.x+","+p.y+")");
}
}
}
public static void main(String[] args) {
Point p = new Point(2,3);
Point q = new Point(4,5);
Point r = new Point(6,7);
test(p, q, r);
if (new Object() instanceof Point q) // compile-time error
System.out.println("I get your point");
}
}
Некоторые объявления могут быть затенены в части своего области видимости другим объявлением с тем же именем, в этом случае простое имя не может быть использовано для ссылки на объявленный объект.
Затенение отличается от скрытия (§8.3, §8.4.8.2, §8.5, §9.3, §9.5), которое применяется только к членам, которые в противном случае были бы унаследованы, но не унаследованы из-за объявления в подклассе. Затенение также отличается от затемнения (§6.4.2).
Объявление d типа с именем n затеняет объявления других типов с именем n, которые находятся в области видимости в точке, где происходит d, на протяжении всей области видимости d.
Объявление d поля или формального параметра с именем n затеняет, на протяжении всей области видимости d, объявления других переменных с именем n, которые находятся в области видимости в точке, где происходит d.
Объявление d локальной переменной или параметра исключения с именем n затеняет, на протяжении всей области видимости d, (a) объявления других полей с именем n, которые находятся в области видимости в точке, где происходит d, и (b) объявления других переменных с именем n, которые находятся в области видимости в точке, где происходит d, но не объявлены в самом внутреннем классе, в котором объявлено d.
Объявление d метода с именем n затеняет объявления любых других методов с именем n, которые находятся в окружающем пространстве видимости в точке, где происходит d, на протяжении всей области видимости d.
Объявление пакета никогда не затеняет другие объявления.
Объявление импорта типа по требованию никогда не затеняет другие объявления.
Объявление импорта статических элементов по требованию никогда не затеняет другие объявления.
Объявление импорта одного типа d в единице компиляции c пакета p, которое импортирует тип с именем n, затеняет, на протяжении всей c, объявления:
-
любой тип верхнего уровня с именем n, объявленный в другой единице компиляции p
-
любой тип с именем n, импортированный объявлением импорта типа по требованию в c
-
любой тип с именем n, импортированный объявлением импорта статических элементов по требованию в c
Объявление импорта одного статического поля d в единице компиляции c пакета p, которое импортирует поле с именем n, затеняет объявление любого статического поля с именем n, импортированного объявлением импорта статических элементов по требованию в c, на протяжении всей c.
Объявление импорта одного статического метода d в единице компиляции c пакета p, которое импортирует метод с именем n со сигнатурой s, затеняет объявление любого статического метода с именем n со сигнатурой s, импортированного объявлением импорта статических элементов по требованию в c, на протяжении всей c.
Объявление импорта одного статического типа 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. Действительно, этот idiom обычно встречается в конструкторах (§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.2 предписывают выбирать переменную вместо типа и тип вместо пакета. Таким образом, иногда может быть невозможно сослаться на тип или пакет по его простому имени, даже если его объявление находится в области видимости и не затенено. Мы говорим, что такое объявление затенено.
Затенение отличается от затенения (§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).
-
Имена полей не могут затенять имена методов.
-
Если имя поля затенено объявлением параметра или локальной переменной, то имя параметра или локальной переменной может быть изменено без влияния на другой код.
Затенение, связанное с именами констант, встречается редко:
-
Имена констант обычно не содержат строчных букв, поэтому они обычно не будут затенять имена пакетов или типов, а также обычно не будут затенять поля, имена которых обычно содержат хотя бы одну строчную букву.
-
Имена констант не могут затенять имена методов, потому что они различаются синтаксически.
Смысл имени зависит от контекста его использования. Определение смысла имени требует трех шагов:
-
Во-первых, контекст приводит к тому, что имя синтаксически попадает в одну из семи категорий: ModuleName, PackageName, TypeName, ExpressionName, MethodName, PackageOrTypeName или AmbiguousName.
TypeName и MethodName менее выразительны, чем пять других категорий, потому что они обозначаются TypeIdentifier и UnqualifiedMethodIdentifier, соответственно (§3.8).
-
Во-вторых, имя, изначально классифицированное контекстом как AmbiguousName или PackageOrTypeName, затем переклассифицируется в PackageName, TypeName или ExpressionName.
-
В-третьих, результирующая категория определяет окончательное значение имени (или ошибку компиляции, если имя не имеет значения).
Использование контекста помогает минимизировать конфликты имен между сущностями разных типов. Такие конфликты будут редки, если будут соблюдаться конвенции именования, описанные в §6.1. Тем не менее, конфликты могут возникать непреднамеренно по мере развития типов, разработанных разными программистами или организациями. Например, типы, методы и поля могут иметь одинаковое имя. Всегда можно отличить метод от поля с одинаковым именем, поскольку контекст использования всегда указывает, предназначен ли метод.
Имя синтаксически классифицируется как ModuleName в этих контекстах:
Имя синтаксически классифицируется как PackageName в этих контекстах:
-
Справа от
exportsилиopensв объявлении модуля -
Слева от "
." в квалифицированном PackageName
Имя синтаксически классифицируется как TypeName в этих контекстах:
-
Для именования класса или интерфейса:
-
В директиве
usesилиprovidesв объявлении модуля (§7.7.1) -
В объявлении импорта одного типа (§7.5.1)
-
Слева от
.в объявлении импорта одного статического члена (§7.5.3) -
Слева от
.в объявлении импорта всех статических членов (§7.5.4) -
В части
permitsобъявления класса или интерфейсаsealed(§8.1.6, §9.1.4). -
Слева от
(в объявлении конструктора (§8.8) -
После знака
@в аннотации (§9.7) -
Слева от
.classв литерале класса (§15.8.2) -
Слева от
.thisв квалифицированном выраженииthis(§15.8.4) -
Слева от
.superв выражении доступа к полю суперкласса с помощью super (§15.11.2) -
Слева от
.Identifier или.super.Identifier в выражении вызова метода (§15.12) -
Слева от
.super::в выражении ссылки на метод (§15.13)
-
-
Как Identifier или последовательность Identifier через точки, составляющие любой ReferenceType (включая ReferenceType слева от скобок в типе массива, или слева от < в параметризованном типе, или в недиких аргументах типа параметризованного типа, или в
extendsилиsuperчасти аргументов типа дикого типа параметризованного типа) в 17 контекстах, где используются типы (§4.11):-
В части
extendsилиimplementsобъявления класса (§8.1.4, §8.1.5) -
В части
extendsобъявления интерфейса (§9.1.3) -
Тип возвращаемого значения метода (§8.4.5, §9.4), включая тип элемента интерфейса аннотации (§9.6.1)
-
В части
throwsметода или конструктора (§8.4.6, §8.8.5, §9.4) -
В части
extendsобъявления параметра типа дженерического класса, интерфейса, метода или конструктора (§8.1.2, §9.1.2, §8.4.4, §8.8.4) -
Тип в объявлении формального параметра метода, конструктора или лямбда-выражения (§8.4.1, §8.8.1, §9.4, §15.27.1)
-
Тип параметра получателя метода (§8.4)
-
Тип в объявлении локальной переменной в операторе (§14.4.2, §14.14.1, §14.14.2, §14.20.3) или шаблоне (§14.30.1)
-
Тип в объявлении параметра исключения (§14.20)
-
Тип в объявлении компонента записи класса записи (§8.10.1)
-
В явном списке аргументов типа для явного вызова конструктора, выражения создания экземпляра класса или выражения вызова метода (§8.8.7.1, §15.9, §15.12)
-
В выражении создания экземпляра класса без указания типа, либо как тип класса, подлежащего созданию (§15.9), или как непосредственный суперкласс или непосредственный суперинтерфейс анонимного класса, подлежащего созданию (§15.9.5)
-
Тип элемента в выражении создания массива (§15.10.1)
-
Тип в операторе приведения типа в выражении приведения (§15.16)
-
Тип, который следует за оператором отношения
instanceof(§15.20.2) -
В выражении ссылки на метод (§15.13), как тип ссылки для поиска метода-члена, или как тип класса или типа массива для построения.
-
Извлечение TypeName из идентификаторов ReferenceType в 16 контекстах выше предназначено для рекурсивного применения ко всем подвыражениям ReferenceType, таким как его элементный тип и любые аргументы типа.
Например, предположим, что объявление поля использует тип p.q.Foo[]. Скобки типа массива игнорируются, и термин p.q.Foo извлекается как последовательность через точку Identifiers слева от скобок в типе массива и классифицируется как TypeName. На более позднем этапе определяется, является ли p, q или Foo именем типа или именем пакета.
В качестве другого примера, предположим, что оператор приведения использует тип p.q.Foo<? extends String>. Термин p.q.Foo вновь извлекается как последовательность через точку Identifier терминов, на этот раз слева от < в параметризованном типе, и классифицируется как TypeName. Термин String извлекается как Identifier в разделе extends аргумента типа с подстановочным знаком параметризованного типа и классифицируется как TypeName.
Имя синтаксически классифицируется как ExpressionName в этих контекстах:
-
В качестве квалифицирующего выражения в вызове конструктора суперкласса с квалификатором (§8.8.7.1)
-
В качестве квалифицирующего выражения в выражении создания экземпляра класса (§15.9)
-
В качестве выражения ссылки на массив в выражении доступа к массиву (§15.10.3)
-
В качестве PostfixExpression (§15.14)
-
В качестве левого операнда оператора присваивания (§15.26)
-
В качестве VariableAccess в операторе
tryс ресурсами (§14.20.3)
Имя синтаксически классифицируется как MethodName в этом контексте:
-
Перед "
(" в выражении вызова метода (§15.12)
Имя синтаксически классифицируется как PackageOrTypeName в этих контекстах:
-
Слева от "
." в квалифицированном TypeName -
В объявлении импорта типа по требованию (§7.5.2)
Имя синтаксически классифицируется как AmbiguousName в этих контекстах:
-
Слева от "
." в квалифицированном ExpressionName -
Слева от самого правого
., который встречается перед "(" в выражении вызова метода -
Слева от "
." в квалифицированном AmbiguousName -
В разделе по умолчанию значения объявления элемента аннотации (§9.6.2)
-
Справа от "
=" в паре значений элемента аннотации (§9.7.1) -
Слева от
::в выражении ссылки на метод (§15.13)
Эффект синтаксической классификации заключается в ограничении определенных видов сущностей определенными частями выражений:
-
Имя поля, параметра или локальной переменной может использоваться как выражение (§15.14.1).
-
Имя метода может появляться в выражении только как часть выражения вызова метода (§15.12).
-
Имя класса или интерфейса может появляться в выражении только как часть литерала класса (§15.8.2), квалифицированного выражения
this(§15.8.4), выражения создания экземпляра класса (§15.9), выражения создания массива (§15.10.1), выражения приведения типа (§15.16), выраженияinstanceof(§15.20.2), константы перечисления (§8.9) или как часть квалифицированного имени для поля или метода. -
Имя пакета может появляться в выражении только как часть квалифицированного имени для класса или интерфейса.
Имя AmbiguousName затем переклассифицируется следующим образом.
Если AmbiguousName является простым именем, состоящим из одного Identifier, то:
-
Если Identifier появляется в области видимости объявления (§6.3), обозначающего локальную переменную, формальный параметр, параметр исключения или поле с таким именем (§14.4, §8.4.1, §8.8.1, §15.27.1, §14.20, §8.3), то AmbiguousName переклассифицируется как ExpressionName.
-
В противном случае, если Identifier является допустимым TypeIdentifier (§3.8) и появляется в области видимости объявления, обозначающего класс, интерфейс или параметр типа с таким именем (§8.1, §9.1, §8.4.4, §8.8.4), то AmbiguousName переклассифицируется как TypeName.
-
В противном случае, AmbiguousName переклассифицируется как PackageName. На более позднем этапе определяется, существует ли пакет с таким именем.
Если AmbiguousName является квалифицированным именем, состоящим из имени, "." и Identifier, то имя слева от "." сначала переклассифицируется, так как оно само является AmbiguousName. Затем возникает выбор:
-
Если имя слева от "
." переклассифицируется как PackageName, то:-
Если Identifier является допустимым TypeIdentifier, и существует пакет, имя которого совпадает с именем слева от "
.", и этот пакет содержит объявление типа с именем, совпадающим с Identifier, то это AmbiguousName переклассифицируется как TypeName. -
В противном случае, это AmbiguousName переклассифицируется как PackageName. На более позднем этапе определяется, существует ли пакет с таким именем.
-
-
Если имя слева от "
." переклассифицируется как TypeName, то:-
Если Identifier является именем метода или поля типа, обозначаемого TypeName, то это AmbiguousName переклассифицируется как ExpressionName.
-
В противном случае, если Identifier является допустимым TypeIdentifier и является именем типа-члена типа, обозначаемого TypeName, то это AmbiguousName переклассифицируется как TypeName.
-
В противном случае возникает ошибка времени компиляции.
-
-
Если имя слева от "
." переклассифицируется как ExpressionName, то это AmbiguousName переклассифицируется как ExpressionName. На более позднем этапе определяется, существует ли член с именем Identifier.
Требование, чтобы потенциальное имя типа было "допустимым TypeIdentifier", предотвращает обработку var и yield как имени типа. Обычно это избыточно, потому что правила объявлений уже предотвращают введение типов с именами var и yield. Однако в некоторых случаях компилятор может найти двоичный класс с именем var или yield, и мы хотим чётко указать, что такие классы никогда не могут быть именованы. Простейшим решением является постоянная проверка на допустимый TypeIdentifier.
Пример 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 классифицируется как ExpressionName, поскольку оно функционирует как PostfixExpression. Поэтому каждое из имён:
org.rpgpoet.Music.wizards org.rpgpoet.Music org.rpgpoet org
сначала классифицируется как AmbiguousName. Затем они переклассифицируются:
-
Простое имя
orgпереклассифицируется как PackageName (поскольку в области видимости нет переменной или типа с именем org). -
Далее, предполагая, что в любом модуле компиляции пакета
orgнет класса или интерфейса с именемrpgpoet(и мы знаем, что такого класса или интерфейса нет, потому что пакетorgсодержит подпакет с именемrpgpoet), квалифицированное имяorg.rpgpoetпереклассифицируется как PackageName. -
Далее, поскольку пакет
org.rpgpoetимеет доступный (§6.6) тип интерфейса с именемMusic, квалифицированное имяorg.rpgpoet.Musicпереклассифицируется как TypeName. -
Наконец, поскольку имя
org.rpgpoet.Musicявляется TypeName, квалифицированное имяorg.rpgpoet.Music.wizardsпереклассифицируется как ExpressionName.
Имя модуля M, простое или квалифицированное, обозначает модуль (если таковой имеется) с этим именем.
Данный раздел не требует ошибки компиляции, если модуль с таким именем не наблюдается. Вместо этого директива requires в объявлении модуля (§7.7.1) выполняет собственную проверку имени модуля, в то время как директивы exports и opens (§7.7.2) допускают отсутствие имён модулей.
Значение имени, классифицированного как PackageName, определяется следующим образом.
Если имя пакета состоит из одного Identifier, то идентификатор должен появляться в области видимости ровно одного объявления верхнего уровня пакета с этим именем (§6.3), и этот пакет должен быть единственным видимым для текущего модуля (§7.4.3), в противном случае возникает ошибка времени компиляции. Значение имени пакета — это этот пакет.
Если имя пакета имеет вид Q.Id, то Q также должно быть именем пакета. Имя пакета Q.Id обозначает пакет, который является членом с именем Id внутри пакета, названного Q.
Если Q.Id не обозначает пакет, который единственным образом виден текущему модулю (§7.4.3), то возникает ошибка времени компиляции.
Если PackageOrTypeName, Q, является допустимым TypeIdentifier и встречается в области действия класса, интерфейса или параметра типа, названного Q, то PackageOrTypeName переквалифицируется как TypeName.
В противном случае PackageOrTypeName переквалифицируется как PackageName. Значение PackageOrTypeName — это значение переквалифицированного имени.
Для квалифицированного PackageOrTypeName вида Q.Id, если Id является допустимым TypeIdentifier и класс, интерфейс, параметр типа или пакет, обозначенный Q, имеет член-класс или интерфейс, названный Id, то имя квалифицированного PackageOrTypeName переквалифицируется как TypeName.
В противном случае оно переквалифицируется как PackageName. Значение квалифицированного PackageOrTypeName — это значение переквалифицированного имени.
Значение имени, классифицированного как TypeName, определяется следующим образом.
Если имя типа состоит из одного Identifier, то идентификатор должен встречаться в области действия ровно одного объявления класса, интерфейса или параметра типа с этим именем (§6.3), иначе возникает ошибка компиляции.
Если объявление относится к параметру типа обобщённого класса или интерфейса C (§8.1.2, §9.1.2), то должны выполняться оба следующих условия, иначе возникает ошибка компиляции:
-
Имя типа не встречается в статическом контексте (§8.1.3).
-
Если имя типа появляется в объявлении вложенного класса или интерфейса C, то непосредственное окружающее объявление класса или интерфейса имени типа является внутренним классом C.
Например, имя типа не должно появляться в теле метода static, объявленного C, а также в теле экземплярного метода класса static, вложенного в C.
Если объявление относится к параметру типа обобщённого метода или конструктора m (§8.4.4, §8.8.4), и имя типа появляется напрямую или косвенно в теле локального класса, локального интерфейса или анонимного класса D, объявленного непосредственно в теле m, то должны выполняться оба следующих условия, иначе возникает ошибка компиляции:
-
Имя типа не встречается в статическом контексте.
-
D является внутренним классом, и непосредственное окружающее объявление класса или интерфейса имени типа — это D или внутренний класс D.
Например, имя типа не должно появляться в теле метода static, объявленного D, а также (если D — локальный интерфейс) в теле метода по умолчанию D.
Значение имени типа — это находящийся в области действия класс, интерфейс или параметр типа.
Пример 6.5.5.1-1. Ссылок на параметры типа
class Box<T> {
T val;
Box(T t) { val = t; }
static Box<T> empty() { // compile-time error
return new Box<>(null);
}
static <U> Box<U> make(U val) {
interface Checker {
void check(U val); // compile-time error
}
class NullChecker implements Checker {
public void check(U val) {
if (val == null) {
throw new IllegalArgumentException();
}
}
}
new NullChecker().check(val);
return new Box<U>(val);
}
}
Параметр типа класса T находится в области действия во всём объявлении класса Box; однако использование имени T в объявлении метода static empty является недопустимым.
Аналогично, параметр типа метода U находится в области действия во всём объявлении метода make; однако использование имени U в объявлении (неявно static) локального интерфейса Checker является недопустимым.
Если имя типа имеет вид Q.Id, то Q должно быть именем класса, интерфейса или параметра типа в пакете, уникально видимом для текущего модуля, или именем пакета, уникально видимого для текущего модуля (§7.4.3).
Если Id указывает ровно один доступный класс или интерфейс (§6.6), являющийся членом класса, интерфейса, параметра типа или пакета, обозначенного Q, то квалифицированное имя типа обозначает этот класс или интерфейс.
Если Id не указывает член-класс или интерфейс в Q (§8.5, §9.5), или член-класс или интерфейс, названный Id в Q, недоступен, или Id указывает более одного члена-класса или интерфейса в 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, определяется следующим образом.
Если имя выражения состоит из одного Идентификатора, то в области видимости в точке, где появляется идентификатор, должно быть ровно одно объявление, обозначающее локальную переменную, формальный параметр, параметр исключения или поле. В противном случае возникает ошибка компиляции.
Если объявление обозначает переменную-инстанс класса C (§8.3.1.1), то должны быть истинны оба следующих утверждения, в противном случае возникает ошибка компиляции:
-
Имя выражения не используется в статическом контексте (§8.1.3).
-
Если имя выражения появляется во вложенном классе или интерфейсе объявления C, то непосредственно окружающее объявление класса или интерфейса имени выражения является внутренним классом C.
Например, имя выражения не должно появляться в теле метода static, объявленного классом C, ни в теле метода-инстанса класса static, вложенного во внутренний класс C.
Если объявление обозначает локальную переменную, формальный параметр или параметр исключения, пусть X — самое внутреннее объявление метода, объявление конструктора, инициализатор инстанса, статический инициализатор, объявление поля или явное вызов конструктора, которое заключает объявление локальной переменной или параметра. Если имя выражения появляется непосредственно или косвенно в теле локального класса, локального интерфейса или анонимного класса D, объявленного непосредственно в X, то должны быть истинны оба следующих утверждения, в противном случае возникает ошибка компиляции:
-
Имя выражения не используется в статическом контексте.
-
D является внутренним классом, и непосредственно окружающее объявление класса или интерфейса имени выражения является D или внутренним классом D.
Например, имя выражения не должно появляться в теле метода static, объявленного классом D, ни (если D — локальный интерфейс) в теле метода по умолчанию D.
Если объявление обозначает локальную переменную, формальный параметр или параметр исключения, которые не являются final и не являются эффективно final (§4.12.4), то возникает ошибка компиляции, если имя выражения появляется в внутреннем классе, заключённом непосредственно или косвенно в X, или в лямбда-выражении, содержащемся в X (§15.27).
Чистым результатом этих правил является то, что к локальной переменной, формальному параметру или параметру исключения можно обратиться из вложенного класса или интерфейса, объявленного в пределах его области видимости, только если (i) обращение не находится в статическом контексте, (ii) существует цепочка вложенных (не-static) классов от ссылки до объявления переменной, и (iii) переменная является final или эффективно final. Ссылки из лямбда-выражений также требуют, чтобы переменная была final или эффективно final.
Если объявление объявляет 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
Пример 6.5.6.1-2. Ссылки на переменные-инстансы
class Test {
static String a;
String b;
String concat1() {
return a + b;
}
static String concat2() {
return a + b; // compile-time error
}
int index() {
interface I {
class Matcher {
void check() {
if (a == null ||
b == null) { // compile-time error
throw new IllegalArgumentException();
}
}
int match(String s, String t) {
return s.indexOf(t);
}
}
}
I.Matcher matcher = new I.Matcher();
matcher.check();
return matcher.match(a, b);
}
}
Поля a и b находятся в области видимости во всем теле класса Test. Однако использование имени b в статическом контексте метода concat2 или в объявлении вложенного класса Matcher, который не является внутренним классом Test, является недопустимым.
Пример 6.5.6.1-3. Ссылки на локальные переменные и формальные параметры
class Test {
public static void main(String[] args) {
String first = args[0];
class Checker {
void checkWhitespace(int x) {
String arg = args[x];
if (!arg.trim().equals(arg)) {
throw new IllegalArgumentException();
}
}
static void checkFlag(int x) {
String arg = args[x]; // compile-time error
if (!arg.startsWith("-")) {
throw new IllegalArgumentException();
}
}
static void checkFirst() {
Runnable r = new Runnable() {
public void run() {
if (first == null) { // compile-time error
throw new IllegalArgumentException();
}
}
};
r.run();
}
}
final Checker c = new Checker();
c.checkFirst();
for (int i = 1; i < args.length; i++) {
Runnable r = () -> {
c.checkWhitespace(i); // compile-time error
c.checkFlag(i); // compile-time error
};
}
}
}
Формальный параметр args находится в области видимости во всем теле метода main. args является эффективно final, поэтому имя args может использоваться в методе-инстансе checkWhitespace локального класса Checker. Однако использование имени args в статическом контексте метода checkFlag локального класса Checker является недопустимым.
Локальная переменная first находится в области видимости до конца тела метода main. first также эффективно final. Однако анонимный класс, объявленный в checkFirst, не является внутренним классом Checker, поэтому использование имени first в теле анонимного класса является недопустимым. (Лямбда-выражение в теле checkFirst аналогично не сможет ссылаться на first, так как лямбда-выражение будет находиться в статическом контексте.)
Локальная переменная c находится в области видимости в последние несколько строк тела метода main и объявлена final, поэтому имя c может использоваться в теле лямбда-выражения.
Локальная переменная i находится в области видимости во всем цикле for. Однако i не является эффективно final, поэтому использование имени i в теле лямбда-выражения является недопустимым.
Если имя выражения имеет вид Q.Id, то Q уже был классифицирован как имя пакета, имя типа или имя выражения.
Если Q является именем пакета, то возникает ошибка компиляции.
Если Q является именем типа, обозначающим тип класса, то:
-
Если не ровно один доступный член (§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 является именем типа, обозначающим тип интерфейса, то:
-
Если не ровно один доступный член типа интерфейса, являющийся полем с именем
Id, то возникает ошибка компиляции. -
В противном случае,
Q.Idобозначает значение поля.Тип выражения
Q.Id— это объявленный тип поля после преобразования захвата (§5.1.10).Если
Q.Idпоявляется в контексте, требующем переменную, а не значение, то возникает ошибка компиляции.
Если Q — это имя выражения, пусть T — тип выражения Q:
-
Если T не является ссылочным типом, возникает ошибка компиляции.
-
Если не ровно один доступный член типа T, являющийся полем с именем
Id, то возникает ошибка компиляции. -
В противном случае, если это поле является одним из следующих:
-
Поле типа интерфейса
-
finalполе типа класса (которое может быть либо классовой, либо экземплярной переменной) -
finalполеlengthмассива типа (§10.7)
то
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, кажется, является сыром типом, но это не так; скорее, это имя не-обобщенного типа Foo, статический член которого должен быть получен (§6.1). Так как не используется сырой тип, нет никаких предупреждений о невыполненной проверке.
Значение имени, классифицированного как MethodName, определяется следующим образом.
Простое имя метода появляется в контексте выражения вызова метода (§15.12). Простое имя метода состоит из одного UnqualifiedMethodIdentifier, которое указывает имя вызываемого метода. Правила вызова метода требуют, чтобы UnqualifiedMethodIdentifier обозначал метод, находящийся в области видимости в момент вызова метода. Правила также запрещают (§15.12.3) ссылку на метод экземпляра в статическом контексте (§8.1.3), или во вложенном классе или интерфейсе, отличном от вложенного класса класса или интерфейса, который объявляет метод экземпляра.
Пример 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).
Язык программирования Java предоставляет механизмы контроля доступа, чтобы предотвратить зависимость пользователей от пакета или класса от ненужных деталей реализации этого пакета или класса. Если доступ разрешён, то доступная сущность называется доступной.
Обратите внимание, что доступность является статическим свойством, которое можно определить во время компиляции; она зависит только от типов и модификаторов объявления.
Квалифицированные имена — это способ доступа к членам пакетов, классов, интерфейсов, параметров типа и типов ссылок. Когда имя такого члена классифицируется из своего контекста (§6.5.1) как квалифицированное имя типа (обозначающее член пакета, класса, интерфейса или параметра типа) или квалифицированное имя выражения (обозначающее член типа ссылки), применяется контроль доступа.
Например, объявление импорта одного типа использует квалифицированное имя типа (§7.5.1), поэтому именованный класс или интерфейс должен быть доступен из единицы компиляции, содержащей объявление import. В качестве другого примера, объявление класса может использовать квалифицированное имя типа для типа суперкласса (§8.1.5), поэтому опять же именованный класс должен быть доступен.
Некоторые очевидные выражения «отсутствуют» в классификации контекста в §6.5.1: доступ к полю на Primary (§15.11.1), вызов метода на Primary (§15.12), ссылка на метод через Primary (§15.13) и созданный класс в квалифицированном создании экземпляра класса (§15.9). Каждое из этих выражений использует идентификаторы, а не имена, по причине, указанной в §6.2. Следовательно, контроль доступа к членам (будь то поля, методы, классы или интерфейсы) применяется явно выражениями доступа к полю, выражениями вызова метода, выражениями ссылки на метод и выражениями квалифицированного создания экземпляра класса. (Обратите внимание, что доступ к полю также может обозначаться квалифицированным именем, встречающимся в качестве постфиксного выражения).
Кроме того, многие операторы и выражения позволяют использовать типы, которые не выражены исключительно с помощью имён типов. Например, объявление класса может использовать параметризованный тип (§4.5) для обозначения типа суперкласса. Поскольку параметризованный тип не является квалифицированным именем типа, для объявления класса необходимо явным образом выполнить контроль доступа для обозначенного суперкласса. Следовательно, из операторов и выражений, предоставляющих контексты в §6.5.1 для классификации TypeName, большинство выполняют свои собственные проверки контроля доступа.
Помимо доступа к членам пакета, класса, интерфейса или параметра типа, существует вопрос доступа к конструкторам класса. Контроль доступа необходимо проверять при явном или неявном вызове конструктора. Следовательно, проверка контроля доступа выполняется явным оператором вызова конструктора (§8.8.7.1) и выражением создания экземпляра класса (§15.9.3). Такие проверки необходимы, потому что §6.5.1 не содержит упоминания явных операторов вызова конструктора (поскольку они ссылаются на конструкторы косвенно, а не через имена) и не знает разницы между классом, обозначаемым выражением создания экземпляра класса без квалификатора, и конструктором этого класса. Кроме того, у конструкторов нет квалифицированных имён, поэтому мы не можем полагаться на проверку контроля доступа во время классификации квалифицированных имён типа.
Доступность влияет на наследование членов класса (§8.2), включая скрытие и переопределение методов (§8.4.8.1).
-
Если класс или интерфейс верхнего уровня (§7.6) объявлен
publicи является членом пакета, экспортируемого модулем, то к классу или интерфейсу может получить доступ любой код в том же модуле, а также любой код в другом модуле, которому экспортирован пакет, при условии, что единица компиляции, в которой объявлен класс или интерфейс, видна этому другому модулю (§7.3). -
Если класс или интерфейс верхнего уровня объявлен
publicи является членом пакета, не экспортируемого модулем, то к классу или интерфейсу может получить доступ любой код в том же модуле. -
Если класс или интерфейс верхнего уровня объявлен с доступом по умолчанию, то к нему можно получить доступ только изнутри пакета, в котором он объявлен.
Класс или интерфейс верхнего уровня, объявленный без модификатора доступа, неявно имеет доступ по умолчанию.
-
Член (класс, интерфейс, поле или метод) класса, интерфейса, параметра типа или типа ссылки, или конструктор класса, доступен только в том случае, если (i) класс, интерфейс, параметр типа или тип ссылки доступны, и (ii) член или конструктор объявлен так, чтобы разрешить доступ:
-
Если член или конструктор объявлен
public, то доступ разрешен.Все члены интерфейсов, не имеющие модификаторов доступа, неявно
public. -
В противном случае, если член или конструктор объявлен
protected, то доступ разрешен только в том случае, если выполняется одно из следующих условий:-
Доступ к члену или конструктору происходит изнутри пакета, содержащего класс, в котором объявлен
protectedчлен или конструктор. -
Доступ корректен, как описано в §6.6.2.
-
-
В противном случае, если член или конструктор объявлен с доступом по умолчанию, то доступ разрешен только при доступе изнутри пакета, в котором объявлен класс, интерфейс, параметр типа или тип ссылки.
Член класса или конструктор, объявленный без модификатора доступа, неявно имеет доступ по умолчанию.
-
В противном случае член или конструктор объявлен
private. Доступ разрешен только в том случае, если доступ происходит внутри тела класса или интерфейса верхнего уровня, который включает объявление члена или конструктора.
-
-
Тип массива доступен тогда и только тогда, когда доступен его тип элементов.
Пример 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.10 для примера использования конструктора private.
Член или конструктор объекта может быть доступен извне пакета, в котором он объявлен, только кодом, ответственным за реализацию этого объекта.
Пусть C — класс, в котором объявлен protected член. Доступ разрешен только внутри тела подкласса S класса C.
Подкласс S рассматривается как ответственный за реализацию объектов класса C. В зависимости от доступности C, S может быть объявлен в том же пакете, что и C, или в другом пакете того же модуля, что и C, или в пакете другого модуля.
Кроме того, доступ к полю экземпляра или методу экземпляра разрешен в зависимости от формы квалифицированного имени, выражения доступа к полю (§15.11), выражения вызова метода (§15.12) или выражения ссылки на метод (§15.13):
-
Если доступ осуществляется с помощью (i) квалифицированного имени вида
ExpressionName.IdилиTypeName.Id, или (ii) выражения доступа к полю видаPrimary.Id, то доступ к полю экземпляраIdразрешен только в том случае, если квалифицирующий тип является S или подклассом S.Квалифицирующий тип — это тип ExpressionName или Primary, или тип, обозначаемый TypeName.
-
Если доступ осуществляется с помощью (i) выражения вызова метода вида
ExpressionName.Id(...)илиTypeName.Id(...)илиPrimary.Id(...), или (ii) выражения ссылки на метод видаExpressionNameили::IdPrimaryили::IdReferenceType, то доступ к методу экземпляра::IdIdразрешен только в том случае, если квалифицирующий тип является S или подклассом S.Квалифицирующий тип — это тип ExpressionName или Primary, или тип, обозначаемый TypeName или ReferenceType.
Дополнительную информацию о доступе к protected членам можно найти в статье «Проверка доступа к защищенным членам в виртуальной машине Java» Алессандро Коглио в журнале «Journal of Object Technology», октябрь 2005 г.
Пусть C — класс, в котором объявлен protected конструктор, и пусть S — внутренний класс, в объявлении которого используется protected конструктор. Тогда:
-
Если доступ осуществляется с помощью вызова конструктора суперкласса
super(...)или квалифицированного вызова конструктора суперклассаE.super(...), гдеE— выражение Primary, то доступ разрешен. -
Если доступ осуществляется с помощью выражения создания экземпляра анонимного класса
newC(...){...}или квалифицированного выражения создания экземпляра анонимного класса E.newC(...){...}, где E — выражение Primary, то доступ разрешен. -
Если доступ осуществляется с помощью выражения создания экземпляра простого класса
newC(...)или квалифицированного выражения создания экземпляра класса E.newC(...), где E — выражение Primary, или выражение ссылки на метод C::new, где C — ClassType, то доступ запрещён. К конструктору сprotectedдоступом можно получить доступ через выражение создания экземпляра класса (не объявляющее анонимный класс) или выражение ссылки на метод только изнутри пакета, в котором он определён.
Пример 6.6.2-1. Доступ к protected полям, методам и конструкторам
Рассмотрим пример, где пакет 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 здесь возникает ошибка компиляции: он не может получить доступ к protected членам x и y своего параметра p, поскольку, хотя Point3d (класс, в котором происходят ссылки на поля x и y) является подклассом Point (класс, в котором объявлены x и y), он не участвует в реализации Point (тип параметра p). Метод delta3d может получить доступ к protected членам своего параметра q, так как класс Point3d является подклассом Point и участвует в реализации Point3d.
Метод delta мог бы попытаться привести (§5.5, §15.16) свой параметр к типу Point3d, но это приведение потерпит неудачу, вызвав исключение, если класс p во время выполнения не являлся бы Point3d.
В методе warp также возникает ошибка компиляции: он не может получить доступ к protected члену z своего параметра a, поскольку, хотя класс Point (класс, в котором происходит ссылка на поле z) участвует в реализации Point3d (тип параметра a), он не является подклассом Point3d (класс, в котором объявлен z).
Каждый примитивный тип, именованный пакет, класс верхнего уровня и интерфейс верхнего уровня имеет полное имя:
-
Полное имя примитивного типа — это ключевое слово для этого примитивного типа, а именно
byte,short,char,int,long,float,doubleилиboolean. -
Полное имя именованного пакета, который не является подпакетом именованного пакета, — это его простое имя.
-
Полное имя именованного пакета, являющегося подпакетом другого именованного пакета, состоит из полного имени содержащего пакета, за которым следует "
.", за которым следует простое (членское) имя подпакета. -
Полное имя класса верхнего уровня или интерфейса верхнего уровня, объявленного в необъявленном пакете, — это простое имя класса или интерфейса.
-
Полное имя класса верхнего уровня или интерфейса верхнего уровня, объявленного в именованном пакете, состоит из полного имени пакета, за которым следует "
.", за которым следует простое имя класса или интерфейса.
Каждый вложенный класс, вложенный интерфейс и тип массива могут иметь полное имя:
-
Вложенный класс или вложенный интерфейс
Mдругого класса или интерфейса C имеет полное имя тогда и только тогда, когда C имеет полное имя.В этом случае полное имя
Mсостоит из полного имени C, за которым следует ".", за которым следует простое имяM. -
Тип массива имеет полное имя тогда и только тогда, когда его элементный тип имеет полное имя.
В этом случае полное имя типа массива состоит из полного имени компонентного типа типа массива, за которым следует "
[]".
Локальный класс, локальный интерфейс или анонимный класс не имеют полного имени.
Каждый примитивный тип, именованный пакет, класс верхнего уровня и интерфейс верхнего уровня имеет каноническое имя:
-
Для каждого примитивного типа, именованного пакета, класса верхнего уровня и интерфейса верхнего уровня каноническое имя совпадает с полным именем.
Каждый вложенный класс, вложенный интерфейс и тип массива могут иметь каноническое имя:
-
Вложенный класс или вложенный интерфейс
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[]".
Пример 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.