Глава 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).
Объявление вводит одну из следующих сущностей в программу:
-
Модуль, объявленный в объявлении модуля (§7.7)
-
Пакет, объявленный в объявлении пакета (§7.4)
-
Импортированный класс или интерфейс, объявленный в объявлении импорта одного типа или объявлении импорта типов по требованию (§7.5.1, §7.5.2)
-
Импортированный член типа, объявленный в объявлении импорта одного статического члена или объявлении импорта статических членов по требованию (§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)
-
Формальный параметр, одно из следующих:
-
Параметр исключения обработчика исключений, объявленного в блоке `try` оператора `try`-`catch` (§14.20)
-
Локальная переменная, одно из следующих:
-
Вложенный класс или интерфейс (§14.3), один из следующих:
-
Вложенный класс, объявленный обычным объявлением класса
-
Вложенный класс, объявленный объявлением перечисления
-
Вложенный класс, объявленный объявлением записи
-
Вложенный интерфейс, объявленный обычным объявлением интерфейса
-
Конструкторы (§8.8, §8.10.4) также вводятся объявлением, но используют имя класса, в котором они объявлены, а не вводят новое имя.
Объявление обычно включает идентификатор (§3.8), который может быть использован в имени для ссылки на объявленную сущность. Идентификатор ограничен, чтобы избежать определённых ключевых слов контекста, когда вводимая сущность является классом, интерфейсом или параметром типа.
Если объявление не включает идентификатор, но вместо этого включает ключевое слово `_` (подчёркивание), то к сущности нельзя обратиться по имени. Следующие виды сущностей могут быть объявлены с помощью подчёркивания:
Локальная переменная, параметр исключения или лямбда-параметр, объявленный с помощью подчёркивания, называется, соответственно, необозначенной локальной переменной, необозначенным параметром исключения или необозначенным лямбда-параметром.
Объявление обобщенного класса или интерфейса (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в выражении доступа к полю суперкласса с использованием `super` (§15.11.2) -
Слева от
.Identifier или.super.Identifier в выражении вызова метода (§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 рекомендуют предоставлять автоматическую поддержку преобразования набора пакетов из локальных и случайных имен пакетов в уникальный формат, описанный здесь.
Если уникальные имена пакетов не используются, могут возникнуть конфликты имен пакетов далеко от точки создания любого из конфликтующих пакетов. Это может создать ситуацию, которую сложно или невозможно решить пользователю или программисту. Классы 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.
Название пакета или модуля не должно подразумевать место его хранения в интернете. Например, пакет с именем edu.cmu.cs.bovik.cheese необязательно доступен с хоста cmu.edu или cs.cmu.edu или bovik.cs.cmu.edu. Предлагаемая конвенция для генерации уникальных имён пакетов и модулей — это просто способ применить конвенцию именования пакетов и модулей к уже существующему широко известному реестру уникальных имён, вместо создания отдельного реестра для имён пакетов и модулей.
Имена классов и интерфейсов
Имена классов должны быть описательными существительными или словосочетаниями, не слишком длинными, с использованием смешанного регистра, где каждая первая буква слова должна быть заглавной.
Пример 6.1-3. Описательные имена классов
ClassLoaderProcessBuilderThreadDictionary 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 Platform, чтобы облегчить их использование.
Имена полей
Имена полей, которые не являются 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). С другой стороны, доступ к полю, квалифицированный вызов метода, ссылки на методы и создание экземпляра класса с квалификацией все ссылаются на члены, имена которых не находятся в лексической среде. По определению, такие имена связываются только в контексте, предоставленном Основным элементом выражения доступа к полю, выражения вызова метода, выражения ссылки на метод или выражения создания экземпляра класса; или super выражения доступа к полю, выражения вызова метода или выражения ссылки на метод; и так далее. Таким образом, мы обозначаем такие члены идентификаторами, а не простыми именами.
Чтобы еще больше усложнить задачу, выражение доступа к полю не является единственным способом обозначения поля объекта. По причинам парсинга квалифицированное имя используется для обозначения поля переменной в области видимости. (Сама переменная обозначается простым именем, упомянутым выше.) Для применения контроля доступа (§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, включая любые вложенные классы или интерфейсы. Если C является записью, то область видимости m дополнительно включает заголовок объявления записи 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 оператора цикла for (§14.14.1) включает всё следующее:
-
Свою собственную инициализацию
-
Любые дальнейшие объявляющие компоненты справа в части ForInit оператора цикла
for -
Части Expression и ForUpdate оператора цикла
for -
Содержащийся оператор Statement
Область видимости локальной переменной, объявленной в заголовке оператора цикла for-each усовершенствованного for (§14.14.2), — это содержащийся оператор Statement.
Область видимости локальной переменной, объявленной в спецификации ресурсов оператора try-with-resources try (§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при истинном значении тогда и только тогда, когда она вводится либо (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при ложном значении тогда и только тогда, когда она вводится либо (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при ложном значении, точно сопоставляется в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).
Переменная шаблона не может скрывать другую локальную переменную (§6.4).
Следует отметить, что нет правила для введения переменной шаблона оператором a instanceof p при ложном значении.
Следующее правило применяется к выражению switch со блоком switch, состоящим из правил switch (§14.11.1):
-
Переменная шаблона, введенная меткой switch, определённо соответствует выражению правила switch, блоку правила switch или оператору правила switch
throw.
Следующие правила применяются к выражению switch с блоком switch, состоящим из групп операторов switch с метками (§14.11.1):
-
Переменная шаблона, введенная меткой switch, определённо соответствует всем операторам связанной группы операторов switch с метками.
-
Переменная шаблона, введенная оператором S, содержащимся в группе операторов switch с метками, определённо соответствует всем операторам, следующим за 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. -
Переменная шаблона вводится оператором
if(e)S тогда и только тогда, когда (i) она вводится операторомeпри ложности и (ii) S не может завершиться нормально.
Правило о том, что оператор if-then вводит переменную шаблона, опирается на понятие «не может завершиться нормально» (§14.22), которое, в свою очередь, опирается на понятие константного выражения (§15.29). Это означает, что для вычисления области действия переменной шаблона может потребоваться определить, относится ли простое имя или квалифицированное имя вида TypeName . Identifier к константному значению. Поскольку переменные шаблонов никогда не могут ссылаться на константу, цикличности не возникает.
Следующие правила применяются к оператору if (e) S else T (§14.9.2):
-
Переменная шаблона, введенная
eпри истинности, определенно сопоставляется с S. -
Переменная шаблона, введенная
eпри ложности, определенно сопоставляется с T. -
Переменная шаблона вводится оператором
if(e)SelseT тогда и только тогда, когда выполняется одно из следующих условий:-
Она вводится оператором
eпри истинности, и S может завершиться нормально, и T не может завершиться нормально; или -
Она вводится оператором
eпри ложности, и S не может завершиться нормально, и T может завершиться нормально.
-
Эти правила подчеркивают поточную природу области действия для переменных шаблонов. Например, в следующем операторе:
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. -
Переменная шаблона вводится оператором
while(e)S тогда и только тогда, когда (i) она вводится операторомeпри ложности и (ii) S не содержит достижимого оператораbreak, для которого операторwhileявляется целью оператора break (§14.15).
Следующее правило применяется к оператору do S while (e) (§14.13):
-
Переменная шаблона вводится оператором
doSwhile(e)тогда и только тогда, когда (i) она вводится операторомeпри ложности и (ii) S не содержит достижимого оператораbreak, для которого операторdoявляется целью оператора break (§14.15).
Следующие правила применяются к оператору basic for (§14.14.1):
-
Переменная шаблона, введенная в выражении условия при истинности, определенно сопоставляется как с частью инкрементации, так и с вложенным оператором.
-
Переменная шаблона вводится оператором basic
forтогда и только тогда, когда (i) она вводится выражением условия при ложности и (ii) вложенный оператор S не содержит достижимого оператораbreak, для которого оператор basicforявляется целью оператора break (§14.15).
Оператор enhanced for (§14.14.2) определяется переводом в оператор basic for, поэтому для него не требуется специальных правил.
Следующее правило применяется к switch оператору со блоком switch, состоящим из правил switch (§14.11.1):
-
Переменная шаблона, введённая меткой switch, определённо сопоставлена в выражении правила switch, блоке switch или операторе switch
throw.
Следующие правила применяются к оператору switch со блоком switch, состоящим из групп меченых операторов switch (§14.11.1):
-
Переменная шаблона, введённая меткой switch, определённо сопоставлена во всех операторах связанной группы меченых операторов switch.
-
Переменная шаблона, введённая оператором S, содержащимся в группе операторов switch, определённо сопоставлена во всех операторах, следующих за S, если таковые имеются, в группе операторов switch.
Переменные шаблонов могут быть введены case метками с case шаблоном, либо самим шаблоном, либо защитой, и находятся в области действия соответствующих частей связанного switch выражения (§6.3.1.6) или switch оператора (§6.3.2.6).
Следующие правила применяются к case меткам:
-
Переменная шаблона вводится
caseметкой сcaseшаблономp, еслиpсодержит объявление переменной шаблона. -
Если
caseшаблон в защищеннойcaseметке содержит объявление переменной шаблона, то переменная шаблона определённо сопоставлена в связанной защите. -
Переменная шаблона вводится защищенной
caseметкой, если она вводится связанной защитой, когда она истинна (§6.3.1).
Локальная переменная (§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, (а) объявления любых других полей с именем 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).
-
Имена полей не могут скрывать имена методов.
-
Если имя поля затеняется объявлением параметра или локальной переменной, то имя параметра или локальной переменной можно изменить без влияния на другой код.
Скрытие, связанное с именами констант, встречается редко:
-
Имена констант обычно не содержат строчных букв, поэтому они обычно не будут скрывать имена пакетов или типов, а также не будут обычно затенять поля, имена которых обычно содержат по крайней мере одну строчную букву.
-
Имена констант не могут скрывать имена методов, потому что они различаются синтаксически.
Значение имени зависит от контекста, в котором оно используется. Определение значения имени требует трёх шагов:
-
Во-первых, контекст приводит к тому, что имя синтаксически принадлежит к одной из семи категорий: ИмяМодуля, ИмяПакеты, ИмяТипа, ИмяВыражения, ИмяМетода, ИмяПакетыИлиТипа или НеоднозначноеИмя.
ИмяТипа и ИмяМетода менее выразительны, чем пять других категорий, потому что они обозначаются ИдентификаторомТипа и НеквалифицированнымИдентификаторомМетода соответственно (§3.8).
-
Во-вторых, имя, изначально классифицированное контекстом как НеоднозначноеИмя или как ИмяПакетыИлиТипа, затем переклассифицируется в ИмяПакеты, ИмяТипа или ИмяВыражения.
-
В-третьих, полученная категория определяет окончательное значение имени (или ошибку компиляции, если имя не имеет смысла).
Использование контекста помогает минимизировать конфликты имён между сущностями разных типов. Такие конфликты будут редкими, если соблюдаются правила именования, описанные в §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в квалифицированном выражении доступа к полю суперкласса (§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 в 17 контекстах выше предназначено для рекурсивного применения ко всем подтермам 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-with-resources (§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, определяется следующим образом.
Если имя выражения состоит из одного Идентификатора, то:
-
Если имя выражения появляется как CaseConstant в метке switch (§14.11.1), и тип выражения-селектора окружающего
switchоператора илиswitchвыражения является типом перечисления (§8.9), и класс перечислений объявляет константу перечисления с именем Идентификатор, то имя выражения ссылается на соответствующее неявное поле класса перечислений. -
В противном случае, если в области видимости в точке, где встречается идентификатор, существует ровно одна декларация, обозначающая локальную переменную, формальный параметр, параметр исключения или поле, то имя выражения ссылается на переменную в области видимости.
-
В противном случае возникает ошибка времени компиляции.
Если объявление обозначает переменную-член класса 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, то возникает ошибка времени компиляции. -
В противном случае, если это поле является одним из следующих:
-
Поле типа интерфейса
-
Статическое поле типа класса (которое может быть статической или нестатической переменной)
-
Поле
finallengthмассива (§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). Поскольку сырые типы не используются, предупреждения об отсутствии проверки типов не выдаются.
Значение имени, классифицированного как ИмяМетода, определяется следующим образом.
Простое имя метода появляется в контексте выражения вызова метода (§15.12). Простое имя метода состоит из одного НеквалифицированногоИдентификатораМетода, который указывает имя вызываемого метода. Правила вызова методов требуют, чтобы НеквалифицированныйИдентификаторМетода обозначал метод, который находится в области видимости в точке вызова метода. Правила также запрещают (§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: доступ к полю на Основном (§15.11.1), вызов метода на Основном (§15.12), ссылка на метод через Основной (§15.13) и созданный класс в квалифицированном создании экземпляра класса (§15.9). Каждое из этих выражений использует идентификаторы, а не имена, по причине, указанной в §6.2. Следовательно, управление доступом к членам (будь то поля, методы, классы или интерфейсы) применяется явно выражениями доступа к полям, выражениями вызова методов, выражениями ссылки на методы и выражениями квалифицированного создания экземпляров классов. (Обратите внимание, что доступ к полю также может быть обозначен квалифицированным именем, встречающимся в качестве постфиксного выражения.)
Кроме того, многие инструкции и выражения позволяют использовать типы, которые не выражаются исключительно с помощью имён типов. Например, объявление класса может использовать параметризованный тип (§4.5) для обозначения типа суперкласса. Поскольку параметризованный тип не является квалифицированным именем типа, необходимо, чтобы объявление класса явно выполняло управление доступом для обозначенного суперкласса. Следовательно, из инструкций и выражений, предоставляющих контексты в §6.5.1 для классификации ИмяТипа, большинство выполняют свои проверки управления доступом.
Помимо доступа к членам пакета, класса, интерфейса или параметра типа, существует вопрос доступа к конструкторам класса. Управление доступом должно проверяться при явном или неявном вызове конструктора. Следовательно, управление доступом проверяется явной инструкцией вызова конструктора (§8.8.7.1) и выражением создания экземпляра класса (§15.9.3). Такие проверки необходимы, потому что §6.5.1 не упоминает явные инструкции вызова конструктора (поскольку они ссылаются на конструкторы косвенно, а не через имена) и не знает различия между классом, обозначаемым выражением создания неквалифицированного экземпляра класса, и конструктором этого класса. Кроме того, конструкторы не имеют квалифицированных имён, поэтому мы не можем полагаться на проверку управления доступом во время классификации квалифицированных имён типов.
Доступность влияет на наследование членов класса (§8.2), включая скрытие и переопределение метода (§8.4.8.1).
-
Если класс или интерфейс верхнего уровня (§7.6) объявлен
publicи является членом пакета, экспортируемого модулем, то к классу или интерфейсу можно получить доступ из любого кода в том же модуле и из любого кода в другом модуле, которому экспортирован пакет, при условии, что единица компиляции, в которой объявлен класс или интерфейс, видна этому другому модулю (§7.3). -
Если класс или интерфейс верхнего уровня объявлен
publicи является членом пакета, который не экспортируется модулем, то к классу или интерфейсу можно получить доступ из любого кода в том же модуле. -
Если класс или интерфейс верхнего уровня объявлен с доступом по умолчанию (package access), то к нему можно получить доступ только изнутри пакета, в котором он объявлен.
Класс или интерфейс верхнего уровня, объявленный без модификатора доступа, неявно имеет доступ по умолчанию.
-
Член (класс, интерфейс, поле или метод) класса, интерфейса, параметра типа или типа ссылки, или конструктор класса, доступен только если (i) класс, интерфейс, параметр типа или тип ссылки доступен, и (ii) член или конструктор объявлен для разрешения доступа:
-
Если член или конструктор объявлен
public, то доступ разрешен.Все члены интерфейсов, не имеющие модификаторов доступа, неявно
public. -
В противном случае, если член или конструктор объявлен
protected, то доступ разрешен только в следующих случаях:-
Доступ к члену или конструктору осуществляется изнутри пакета, содержащего класс, в котором объявлен
protectedчлен или конструктор. -
Доступ корректен, как описано в §6.6.2.
-
-
В противном случае, если член или конструктор объявлен с доступом по умолчанию (package access), то доступ разрешен только тогда, когда доступ осуществляется изнутри пакета, в котором объявлен класс, интерфейс, параметр типа или тип ссылки.
Член класса или конструктор, объявленный без модификатора доступа, неявно имеет доступ по умолчанию.
-
В противном случае, член или конструктор объявлен
private. Доступ разрешен только в следующих случаях:-
Доступ осуществляется изнутри тела класса верхнего уровня или интерфейса, который включает объявление члена или конструктора.
-
Доступ осуществляется в
permitsразделе класса верхнего уровня или интерфейса, который включает объявление члена. -
Доступ осуществляется в списке компонентов записи класса записи верхнего уровня, который включает объявление члена.
-
-
-
Тип массива доступен тогда и только тогда, когда доступен его тип элемента.
Пример 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 не указан, член класса или конструктор имеет доступ по умолчанию (package access): он доступен во всем пакете, содержащем объявление класса, в котором объявлен член класса, но член класса или конструктор недоступен ни в каком другом пакете.
Если класс 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 конструктор.
К protected члену или конструктору объекта извне пакета, в котором он объявлен, можно обратиться только коду, отвечающему за реализацию этого объекта.
Пусть 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, то доступ запрещён. К защищённому конструктору можно обратиться с помощью выражения создания экземпляра класса (не объявляющего анонимный класс) или выражения ссылки на метод только изнутри пакета, в котором он определён.
Пример 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.