Глава 6. Имена
Содержание
- 6.1. Объявления
- 6.2. Имена и идентификаторы
- 6.3. Область видимости объявления
- 6.4. Перекрытие и затемнение
- 6.5. Определение значения имени
- 6.6. Управление доступом
- 6.7. Полные имена и канонические имена
Имена используются для ссылки на сущности, объявленные в программе.
Объявленная сущность (§6.1) — это пакет, класс, интерфейс, член (класс, интерфейс, поле или метод) типа ссылки, параметр типа, формальный параметр, параметр исключения или локальная переменная.
Имена в программах могут быть простыми, состоящими из одного идентификатора, или квалифицированными, состоящими из последовательности идентификаторов, разделённых символами "." (§6.2).
Каждое объявление, вводящее имя, имеет область видимости (§6.3), которая представляет собой часть текста программы, в пределах которой объявленную сущность можно указать простым именем.
Квалифицированное имя N.x может использоваться для ссылки на член пакета или типа ссылки, где N — это простое или квалифицированное имя, а x — идентификатор. Если N обозначает пакет, то x является членом этого пакета, который может быть классом, интерфейсом или подпакетом. Если N обозначает тип ссылки или переменную типа ссылки, то x обозначает член этого типа, который может быть классом, интерфейсом, полем или методом.
При определении значения имени (§6.5) используется контекст вхождения, чтобы разрешить неоднозначность между пакетами, типами, переменными и методами с одинаковыми именами.
Управление доступом (§6.6) можно указать в объявлении класса, интерфейса, метода или поля, чтобы контролировать, когда разрешается доступ к члену. Доступ — это понятие отличное от области видимости. Доступ определяет часть текста программы, в которой объявленную сущность можно указать квалифицированным именем. Доступ к объявленной сущности также важен в выражениях доступа к полям (§15.11), выражениях вызова метода, в которых метод не указан простым именем (§15.12), выражениях ссылки на метод (§15.13) или квалифицированном выражении создания экземпляра класса (§15.9). При отсутствии модификатора доступа большинство объявлений имеют доступ к пакету, разрешая доступ к любому месту внутри пакета, содержащего объявление; другие возможности — public, protected и private.
Полные и канонические имена (§6.7) также обсуждаются в этой главе.
END_OF_DOCUMENT_MARKERОбъявление вводит сущность в программу и включает идентификатор (§3.8), который может быть использован в имени для ссылки на эту сущность. Идентификатор ограничен, чтобы избежать определенных ключевых слов контекста, когда вводимая сущность является классом, интерфейсом или параметром типа.
Объявляемая сущность может быть следующей:
-
Модуль, объявленный в объявлении модуля (§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 (§14.20)
-
Локальная переменная, один из следующих:
-
Вложенный класс или интерфейс (§14.3), один из следующих:
-
Вложенный класс, объявленный нормальным объявлением класса
-
Вложенный класс, объявленный объявлением перечисления
-
Вложенный класс, объявленный объявлением записи
-
Вложенный интерфейс, объявленный нормальным объявлением интерфейса
-
Конструкторы (§8.8, §8.10.4) также вводятся объявлениями, но используют имя класса, в котором они объявлены, а не вводят новое имя.
Объявление обобщенного класса или интерфейса (class C<T> ... или interface C<T> ...) вводит как класс с именем C, так и семейство типов: необобщенный тип C, параметризованный тип C<Foo>, параметризованный тип C<Bar> и т. д.
Когда ссылка на C встречается в контексте, где обобщенность не важна, идентифицируемом ниже как один из необобщенных контекстов, эта ссылка на C обозначает класс или интерфейс C. В других контекстах ссылка на C обозначает тип или часть типа, введенного с помощью C.
15 необобщенных контекстов приведены ниже:
-
В директиве
usesилиprovidesв объявлении модуля (§7.7.1) -
В объявлении импорта одного типа (§7.5.1)
-
Слева от
.в объявлении импорта одного статического элемента (§7.5.3) -
Слева от
.в объявлении импорта всех статических элементов (§7.5.4) -
В разделе
permitsобъявления класса или интерфейсаsealed(§8.1.6, §9.1.4). -
Слева от
(в объявлении конструктора (§8.8) -
После знака
@в аннотации (§9.7) -
Слева от
.classв литерале класса (§15.8.2) -
Слева от
.thisв квалифицированном выраженииthis(§15.8.4) -
Слева от
.superв выражении доступа к полю родительского класса с использованием super (§15.11.2) -
Слева от
.Идентификатор или.super.Идентификатор в выражении вызова метода (§15.12) -
Слева от
.super::в выражении ссылки на метод (§15.13) -
В квалифицированном имени выражения в постфиксном выражении или операторе
try-с-ресурсами (§15.14.1, §14.20.3) -
В блоке
throwsметода или конструктора (§8.4.6, §8.8.5, §9.4) -
В объявлении параметра исключения (§14.20)
Первые двенадцать не-генерических контекстов соответствуют первым двенадцати синтаксическим контекстам для TypeName в §6.5.1. Тринадцатый не-генерический контекст — это место, где квалифицированное ExpressionName, такое как C.x, может включать TypeName C для обозначения доступа к статическому члену. Общее использование TypeName в этих тринадцати контекстах важно: оно указывает, что эти контексты включают использование типа с менее чем первоклассным статусом. В отличие от этого, четырнадцатый и пятнадцатый не-генерические контексты используют ClassType, указывая, что в разделах throws и catch типы используются в первоклассном формате, подобно, например, объявлениям полей. Характеристика этих двух контекстов как не-генерических обусловлена тем, что тип исключения не может быть параметризован (§8.1.2).
Обратите внимание, что производство ClassType допускает аннотации, поэтому можно аннотировать использование типа в разделе throws или catch, тогда как производство TypeName запрещает аннотации, поэтому нельзя аннотировать имя типа, например, в объявлении импорта одного типа.
Правила именования
Библиотеки классов Java SE Platform стараются использовать, когда это возможно, имена, выбранные в соответствии с правилами, представленными ниже. Эти правила помогают сделать код более читаемым и избежать определенных типов конфликтов имён.
Мы рекомендуем использовать эти правила во всех программах, написанных на языке программирования Java. Однако следовать этим правилам следует не догматично, если длительное обычное использование диктует иное. Например, методы sin и cos класса java.lang.Math имеют математически общепринятые названия, даже несмотря на то, что эти имена методов нарушают предлагаемую здесь конвенцию, поскольку они короткие и не являются глаголами.
Имена пакетов и модулей
Программисты должны предпринимать шаги, чтобы избежать возможности того, что два опубликованных пакета будут иметь одинаковое имя, выбирая уникальные имена пакетов для пакетов, которые широко распространяются. Это позволяет легко и автоматически устанавливать и каталогизировать пакеты. Этот раздел описывает рекомендуемую конвенцию для генерации таких уникальных имён пакетов. Реализации Java SE Platform рекомендуют предоставлять автоматическую поддержку преобразования набора пакетов из локальных и случайных имён пакетов в уникальный формат имени, описанный здесь.
Если не использовать уникальные имена пакетов, могут возникнуть конфликты имён пакетов далеко от точки создания любого из конфликтующих пакетов. Это может создать ситуацию, которую трудно или невозможно разрешить пользователю или программисту. Классы ClassLoader и ModuleLayer могут использоваться для изоляции пакетов с одинаковыми именами друг от друга в тех случаях, когда пакеты будут иметь ограниченные взаимодействия, но не таким образом, чтобы это было прозрачно для обычной программы.
Вы формируете уникальное имя пакета, сначала имея (или принадлежа, организации, которая имеет) доменное имя в интернете, например oracle.com. Затем вы инвертируете это имя, компонент за компонентом, чтобы в данном примере получить com.oracle, и используйте это в качестве префикса для ваших имён пакетов, используя соглашение, разработанное внутри вашей организации для дальнейшего администрирования имён пакетов. Такое соглашение может предписывать, чтобы определённые компоненты имени пакета были подразделениями, отделами, проектами, машинами или именами входа.
Пример 6.1-1. Уникальные имена пакетов
com.nighthacks.scrabble.dictionary org.openjdk.compiler.source.tree net.jcip.annotations edu.cmu.cs.bovik.cheese gov.whitehouse.socks.mousefinder
Первый компонент уникального имени пакета всегда записывается строчными латинскими буквами ASCII и должен быть одним из доменных имён верхнего уровня, таких как com, edu, gov, mil, net или org, или одним из английских двухбуквенных кодов, определяющих страны, как указано в ISO Standard 3166.
В некоторых случаях доменное имя может не быть допустимым именем пакета. Вот некоторые рекомендуемые соглашения для решения этих ситуаций:
-
Если доменное имя содержит дефис или любой другой специальный символ, недопустимый в идентификаторе (§3.8), замените его на символ подчёркивания.
-
Если любой из полученных компонентов имени пакета является ключевым словом (§3.9), добавьте к нему символ подчёркивания.
-
Если любой из полученных компонентов имени пакета начинается с цифры или любого другого символа, недопустимого в качестве начального символа идентификатора, добавьте символ подчёркивания перед компонентом.
Имя модуля должно соответствовать имени его основного экспортируемого пакета. Если модуль не имеет такого пакета или по причинам совместимости с прошлыми версиями должен иметь имя, не соответствующее одному из его экспортируемых пакетов, то его имя все равно должно начинаться с обращённой формы доменного имени, с которым ассоциирован его автор.
Пример 6.1-2. Уникальные имена модулей
com.nighthacks.scrabble org.openjdk.compiler net.jcip.annotations
Первый компонент имени пакета или модуля не должен быть идентификатором java. Имена пакетов и модулей, начинающиеся с идентификатора java, зарезервированы для пакетов и модулей Java SE Platform.
Имя пакета или модуля не должно подразумевать местоположение пакета или модуля в интернете. Например, пакет с именем edu.cmu.cs.bovik.cheese необязательно доступен с хоста cmu.edu или cs.cmu.edu или bovik.cs.cmu.edu. Предлагаемая конвенция для генерации уникальных имён пакетов и модулей просто способ наложения соглашения об именах пакетов и модулей на существующий широко известный реестр уникальных имён вместо создания отдельного реестра для имён пакетов и модулей.
Имена классов и интерфейсов
Имена классов должны быть описательными существительными или существительными словосочетаниями, не слишком длинными, с использованием смешанного регистра, причём первая буква каждого слова должна быть заглавной.
Пример 6.1-3. Описательные имена классов
ClassLoaderSecurityManagerThreadDictionary BufferedInputStream
Аналогично, имена интерфейсов должны быть короткими и описательными, не чрезмерно длинными, с использованием смешанного регистра, где первая буква каждого слова прописная. Имя может быть описательным существительным или словосочетанием, что подходит, когда интерфейс используется как абстрактный суперкласс, например, интерфейсы java.io.DataInput и java.io.DataOutput; или это может быть прилагательное, описывающее поведение, как для интерфейсов Runnable и Cloneable.
Имена переменных типа
Имена переменных типа должны быть лаконичными (если возможно, одним символом) и выразительными, и не должны содержать строчных букв. Это упрощает различение параметров типа от обычных классов и интерфейсов.
Классы и интерфейсы контейнеров должны использовать имя E для типа их элементов. Карты должны использовать K для типа своих ключей и V для типа своих значений. Имя X следует использовать для произвольных типов исключений. Мы используем T для типа, когда нет ничего более специфичного, чтобы отличить его. (Это часто случается в методах с параметрами типа.)
Если есть несколько параметров типа, обозначающих произвольные типы, следует использовать буквы, расположенные рядом с T в алфавите, например, S. В качестве альтернативы, допустимо использовать числовые индексы (например, T1, T2) для различения различных переменных типа. В таких случаях все переменные с одинаковым префиксом должны иметь индексы.
Если метод с параметрами типа встречается внутри класса с параметрами типа, рекомендуется избегать использования одинаковых имён для параметров типа метода и класса, чтобы избежать путаницы. То же самое относится к вложенным классам с параметрами типа.
Пример 6.1-4. Традиционные имена переменных типа
public class HashSet<E> extends AbstractSet<E> { ... }
public class HashMap<K,V> extends AbstractMap<K,V> { ... }
public class ThreadLocal<T> { ... }
public interface Functor<T, X extends Throwable> {
T eval() throws X;
}
Когда параметры типа не попадают удобно ни в одну из упомянутых категорий, имена должны выбираться максимально осмысленными в пределах одного символа. Упомянутые выше имена (E, K, V, X, T) не должны использоваться для параметров типа, которые не попадают в назначенные категории.
Имена методов
Имена методов должны быть глаголами или словосочетаниями, в смешанном регистре, с первой буквой строчной и первой буквой каждого последующего слова прописной. Вот некоторые дополнительные конкретные соглашения для имён методов:
-
Методы для получения и задания атрибута, который можно рассматривать как переменную V, должны именоваться
getVиsetV. Примером являются методыgetPriorityиsetPriorityклассаThread. -
Метод, возвращающий длину чего-либо, должен именоваться
length, как в классеString. -
Метод, проверяющий булево условие V об объекте, должен именоваться
isV. Примером является методisInterruptedклассаThread. -
Метод, преобразующий свой объект в определённый формат F, должен именоваться
toF. Примеры — методtoStringклассаObjectи методыtoLocaleStringиtoGMTStringклассаjava.util.Date.
Всякий раз, когда это возможно и уместно, наименование методов в новом классе по аналогии с именами в похожем существующем классе, особенно классе из API Java SE 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для произвольного значения некоторого типа
Локальные переменные или имена параметров, состоящие только из двух или трёх строчных букв, не должны совпадать с начальными кодами стран и доменными именами, которые являются первым компонентом уникальных имён пакетов.
Используется имя для ссылки на сущность, объявленную в программе.
Существуют два вида имён: простые имена и квалифицированные имена.
A простое имя представляет собой один идентификатор.
A квалифицированное имя состоит из имени, маркера "." и идентификатора.
При определении значения имени (§6.5), учитывается контекст, в котором появляется имя. Правила §6.5 различают контексты, где имя должно обозначать (ссылаться) на пакет (§6.5.3); класс, интерфейс или параметр типа (§6.5.5); переменную или значение в выражении (§6.5.6); или метод (§6.5.7).
У пакетов, классов, интерфейсов и параметров типа есть члены, которые могут быть доступны по квалифицированным именам. В качестве справочного материала для обсуждения квалифицированных имён и определения значения имён см. описания членства в §4.4, §4.5.2, §4.8, §4.9, §7.1, §8.2, §9.2 и §10.7.
Не все идентификаторы в программе являются частью имени. Идентификаторы также используются в следующих ситуациях:
-
В объявлениях (§6.1), где идентификатор может использоваться для указания имени, по которому будет известна объявленная сущность.
-
В качестве меток в меткированных операторах (§14.7) и в операторах
breakиcontinue(§14.15, §14.16), которые ссылаются на метки операторов.Идентификаторы, используемые в меткированных операторах и их связанных операторах
breakиcontinue, полностью независимы от идентификаторов, используемых в объявлениях. -
В выражениях доступа к полям (§15.11), где идентификатор следует за маркером "
." для указания члена объекта, обозначаемого выражением перед маркером ".", или объекта, обозначаемогоsuperили TypeName.superперед маркером ".". -
В некоторых выражениях вызова метода (§15.12), где идентификатор следует за маркером "
." и предшествует маркером "(" для указания метода, который необходимо вызвать для объекта, обозначаемого выражением перед маркером ".", или типа, обозначаемого TypeName перед маркером ".", или объекта, обозначаемогоsuperили TypeName.superперед маркером ".". -
В некоторых выражениях ссылок на методы (§15.13), где идентификатор следует за маркером "
::" для указания метода объекта, обозначаемого выражением перед маркером "::", или типа, обозначаемого TypeName перед маркером "::", или объекта, обозначаемогоsuperили TypeName.superперед маркером "::". -
В выражениях создания экземпляров класса с квалификатором (§15.9), где идентификатор находится справа от маркера
newдля указания типа, являющегося членом типа выражения, предшествующего маркеруnew. -
В парах имя-значение аннотаций (§9.7.1), для обозначения элемента соответствующего интерфейса аннотаций.
В этой программе:
class Test {
public static void main(String[] args) {
Class c = System.out.getClass();
System.out.println(c.toString().length() +
args[0].length() + args.length);
}
}
идентификаторы Test, main и первые вхождения args и c не являются именами. Скорее, они являются идентификаторами, используемыми в объявлениях для указания имён объявленных сущностей. В примере появляются имена String, Class, System.out.getClass, System.out.println, c.toString, args и args.length.
Вхождение length в args.length является именем, потому что args.length является квалифицированным именем (§6.5.6.2), а не выражением доступа к полю (§15.11). Выражение доступа к полю, а также выражение вызова метода, выражение ссылки на метод и выражение создания экземпляра класса с квалификатором используют идентификатор, а не имя, для обозначения интересующего члена. Таким образом, вхождение length в args[0].length() не является именем, а скорее идентификатором, используемым в выражении вызова метода.
Можно было бы спросить, почему такие выражения используют идентификатор, а не простое имя, которое, в конце концов, просто идентификатор. Причина в том, что простое имя выражения определяется в лексической среде; то есть простое имя выражения должно находиться в области видимости объявления переменной (§6.5.6.1). С другой стороны, доступ к полю, квалифицированный вызов метода, ссылки на методы и создание экземпляров класса с квалификатором все ссылаются на члены, имена которых не находятся в лексической среде. По определению, такие имена связываются только в контексте, предоставленном Основным выражением доступа к полю, выражением вызова метода, выражением ссылки на метод или выражением создания экземпляра класса; или Primary выражения доступа к полю, выражения вызова метода или выражения ссылки на метод; и так далее. Таким образом, мы обозначаем таких членов идентификаторами, а не простыми именами.
Чтобы еще больше усложнить ситуацию, выражение доступа к полю — не единственный способ обозначения поля объекта. По причинам парсинга квалифицированное имя используется для обозначения поля переменной в области видимости. (Сама переменная обозначается простым именем, как упоминалось выше.) Необходимо, чтобы контроль доступа (§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 (§14.14.2) — вложенный оператор Statement.
Область видимости локальной переменной, объявленной в спецификации ресурса оператора try-with-resources (§14.20.3) — от объявления вправо по оставшейся части спецификации ресурса и всему блоку try, связанному с оператором try-with-resources.
Перевод оператора try-with-resources подразумевает вышеприведённое правило.
Область видимости параметра обработчика исключения, объявленного в блоке catch оператора цикла try (§14.20) — весь блок, связанный с catch.
Пример 6.3-1. Область видимости объявлений классов
Эти правила подразумевают, что объявления типов классов и интерфейсов не обязательно должны появляться до использования типов. В следующей программе использование PointList в классе Point допустимо, потому что область видимости объявления класса PointList включает как класс Point, так и класс PointList, а также любые другие объявления классов или интерфейсов в других единицах компиляции пакета points.
package points;
class Point {
int x, y;
PointList list;
Point next;
}
class PointList {
Point first;
}
Пример 6.3-2. Область видимости объявления локальных переменных
В следующей программе возникает ошибка времени компиляции, так как инициализация локальной переменной x находится в области видимости объявления локальной переменной x, но локальная переменная x ещё не имеет значения и не может быть использована. Поле x имеет значение 0 (присвоенное при инициализации Test1), но это ложная информация, так как оно скрывается (§6.4.1) локальной переменной x.
class Test1 {
static int x;
public static void main(String[] args) {
int x = x;
}
}
Следующая программа компилируется:
class Test2 {
static int x;
public static void main(String[] args) {
int x = (x=2)*2;
System.out.println(x);
}
}
потому что локальной переменной x определённо присвоено значение (§16 (Definite Assignment)) перед использованием. Она выводит:
4
В следующей программе инициализатор для three может правильно ссылаться на переменную two, объявленную в более раннем объявленном параметре, и вызов метода в следующей строке может правильно ссылаться на переменную three, объявленную ранее в блоке.
class Test3 {
public static void main(String[] args) {
System.out.print("2+1=");
int two = 2, three = two + 1;
System.out.println(three);
}
}
Эта программа выводит:
2+1=3
Область видимости объявления переменной шаблона (то есть локальной переменной, объявленной шаблоном) — часть программы, которая может быть выполнена после успешного сопоставления значения с шаблоном (§14.30.2). Она определяется путём рассмотрения точек программы, где переменная шаблона определённо сопоставлена в области, начинающейся с шаблона, который объявляет переменную шаблона.
Остальная часть этого раздела посвящена точному объяснению слов "определенно совпало". Анализ учитывает структуру операторов и выражений, с особым учетом операторов булевых выражений и определенных форм операторов.
Будет видно, что область действия переменной шаблона — это понятие, зависящее от потока, аналогичное определённому присваиванию (§16 (Определённое присваивание)). Правила, определённые в остальной части этого раздела, преднамеренно имеют схожую форму с правилами определённого присваивания.
Анализ опирается на технический термин "введённый", который имеет следующий вид:
-
переменная шаблона вводится выражением при истинном значении
-
переменная шаблона вводится выражением при ложном значении
-
переменная шаблона вводится оператором
Самый простой пример — переменная шаблона s вводится выражением a instanceof String s при истинном значении. Другими словами, если значение выражения равно true, то сопоставление шаблонов должно было быть успешным, и, следовательно, переменной шаблона должно было быть присвоено значение.
В отличие от этого, переменная шаблона t вводится выражением !(b instanceof Integer t) при ложном значении. Это происходит потому, что сопоставление шаблонов могло быть успешным только в том случае, если значение выражения равно false.
Введение переменных шаблонов и определение мест их определенного соответствия связано только с определенными видами логических выражений. Если выражение не является выражением условного И, выражением условного ИЛИ, выражением логического отрицания, условным выражением, выражением instanceof, выражением switch или выражением в скобках, то никакие правила области действия не применяются.
Для выражения условного И a && b (§15.23) применяются следующие правила:
-
Переменная шаблона, введённая посредством
aпри истинном значении, определённо сопоставляется вb. -
Переменная шаблона вводится посредством
a&&bпри истинном значении тогда и только тогда, когда она введена либо посредствомaпри истинном значении, либо посредствомbпри истинном значении.
Следует отметить, что нет правила для введения переменной шаблона посредством a && b при ложном значении. Это связано с тем, что на этапе компиляции нельзя определить, какой операнд примет значение false.
Ошибка времени компиляции возникает, если выполняются следующие условия:
-
Переменная шаблона введена как (i) посредством
aпри истинном значении, так и (ii) посредствомbпри истинном значении. -
Переменная шаблона введена как (i) посредством
aпри ложном значении, так и (ii) посредствомbпри ложном значении.
Эти два случая ошибок исключают возможность того, что оба операнда оператора && объявляют переменную шаблона с одинаковым именем. Например, рассмотрим проблемное выражение (a instanceof String s) && (b
instanceof String s). Первый случай ошибки охватывает ситуацию, когда всё выражение принимает значение true, где (если бы код был допустимым) потребовалось бы инициализировать две объявления переменной шаблона s, так как и левосторонний, и правосторонний операнды принимают значение true. Поскольку нет способа различить две переменные, называемые s в остальной части программы, всё выражение считается ошибочным. Второй случай ошибки охватывает противоположную ситуацию, когда всё выражение принимает значение false.
Для выражения условного ИЛИ a || b (§15.24) применяются следующие правила:
-
Переменная шаблона, введённая посредством
aпри ложном значении, определённо сопоставляется вb. -
Переменная шаблона вводится посредством
a||bпри ложном значении тогда и только тогда, когда она введена либо посредствомaпри ложном значении, либо посредствомbпри ложном значении.
Следует отметить, что нет правила для введения переменной шаблона посредством 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).
Следующие правила применяются к оператору цикла for (§14.14.1):
-
Переменная шаблона, введённая в выражении условия при истинности, обязательно сопоставляется как с частью увеличения, так и с содержащимся оператором.
-
Переменная шаблона вводится оператором цикла
forтогда и только тогда, когда (i) она вводится в выражении условия при ложности и (ii) содержащийся оператор S не содержит достижимого оператораbreak, для которого операторforявляется целью оператора break (§14.15).
Оператор расширенного цикла for (§14.14.2) определяется переходом к оператору цикла for, поэтому для него не требуется специальных правил.
Следующее правило применяется к оператору switch с блоком switch, состоящим из правил switch (§14.11.1):
-
Переменная шаблона, введённая меткой switch, определённо соответствует выражению правила switch, блоку правила switch или оператору
throwправила switch.
Следующие правила применяются к оператору switch с блоком switch, состоящим из групп операторов switch с метками (§14.11.1):
-
Переменная шаблона, введённая меткой switch, определённо соответствует всем операторам в связанной группе операторов switch с меткой.
-
Переменная шаблона, введённая оператором S, находящимся в группе операторов блока switch, определённо соответствует всем операторам, следующим за S (если таковые имеются) в группе операторов блока switch.
Следующее правило применяется к оператору с меткой (§14.7):
-
Переменная шаблона вводится оператором с меткой
L:S (гдеL— метка) тогда и только тогда, когда (i) она вводится оператором S, и (ii) S не содержит достижимого оператораbreak, для которого оператор с меткой является целью оператора break (§14.15).
Переменные шаблонов могут вводиться метками case с шаблоном case, либо самим шаблоном, либо условием, и находятся в области видимости соответствующих частей связанного выражения switch (§6.3.1.6) или оператора switch (§6.3.2.6).
Следующие правила применяются к меткам case:
-
Переменная шаблона вводится меткой
caseс шаблономcasep, если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, (a) объявления любых других полей с именем n, которые находятся в области видимости в момент, когда d встречается, и (b) объявления любых других переменных с именем n, которые находятся в области видимости в момент, когда d встречается, но не объявлены в самом внутреннем классе, в котором d объявлено.
Объявление d метода с именем n затеняет объявления любых других методов с именем n, которые находятся во внешней области видимости в момент, когда d встречается, во всей области видимости d.
Объявление пакета никогда не затеняет никакое другое объявление.
Объявление импорта типа по умолчанию никогда не приводит к затенению другого объявления.
Объявление статического импорта по умолчанию никогда не приводит к затенению другого объявления.
Объявление импорта одного типа d в единице компиляции c пакета p, которое импортирует тип с именем n, затеняет, во всей c, объявления:
-
любой тип верхнего уровня с именем n, объявленный в другой единице компиляции p
-
любой тип с именем n, импортированный объявлением импорта типа по умолчанию в c
-
любой тип с именем n, импортированный объявлением статического импорта по умолчанию в c
Объявление одиночного статического импорта d в единице компиляции c пакета p, которое импортирует поле с именем n, затеняет объявление любого статического поля с именем n, импортированного объявлением статического импорта по умолчанию в c, во всей c.
Объявление одиночного статического импорта d в единице компиляции c пакета p, которое импортирует метод с именем n с сигнатурой s, затеняет объявление любого статического метода с именем n с сигнатурой s, импортированного объявлением статического импорта по умолчанию в c, во всей c.
Объявление одиночного статического импорта d в единице компиляции c пакета p, которое импортирует тип с именем n, затеняет, во всей c, объявления:
Пример 6.4.1-1. Затенение объявления поля локальной переменной
class Test {
static int x = 1;
public static void main(String[] args) {
int x = 0;
System.out.print("x=" + x);
System.out.println(", Test.x=" + Test.x);
}
}
Эта программа выводит:
x=0, Test.x=1
Эта программа объявляет:
-
класс
Test -
переменную класса (
static)x, которая является членом классаTest -
метод класса
main, который является членом классаTest -
параметр
argsметодаmain -
локальную переменную
xметодаmain
Поскольку область видимости переменной класса включает весь текст класса (§8.2), переменная класса x обычно доступна во всем теле метода main. Однако в данном примере переменная класса x затеняется в теле метода main объявлением локальной переменной x.
Локальная переменная имеет область видимости остальную часть блока, в котором она объявлена (§6.3); в данном случае это остальная часть тела метода main, а именно его инициализатор "0" и вызовы System.out.print и System.out.println.
Это означает, что:
-
Выражение
xв вызовеprintссылается на (обозначает) значение локальной переменнойx. -
Вызов
printlnиспользует квалифицированное имя (§6.6)Test.x, которое использует имя типа классаTestдля доступа к переменной классаx, потому что объявлениеTest.xзатенено в данном месте и не может быть указано по его простому имени.
Ключевое слово this также может быть использовано для доступа к затенённому полю x, используя форму this.x. Действительно, эта идиома обычно встречается в конструкторах (§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разделе объявления класса или интерфейса (§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-с-ресурсами (§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) или в качестве части квалифицированного имени для поля или метода. -
Имя пакета может появляться в выражении только как часть квалифицированного имени для класса или интерфейса.
Имя НеоднозначноеИмя затем переклассифицируется следующим образом.
Если НеоднозначноеИмя является простым именем, состоящим из одного Идентификатора, то:
-
Если Идентификатор встречается в области действия объявления (§6.3), обозначающего либо локальную переменную, формальный параметр, параметр исключения или поле с таким именем (§14.4, §8.4.1, §8.8.1, §15.27.1, §14.20, §8.3), то НеоднозначноеИмя переклассифицируется как ИмяВыражения.
-
В противном случае, если Идентификатор является допустимым ИдентификаторомТипа (§3.8) и появляется в области действия объявления, обозначающего класс, интерфейс или параметр типа с таким именем (§8.1, §9.1, §8.4.4, §8.8.4), то НеоднозначноеИмя переклассифицируется как ИмяТипа.
-
В противном случае НеоднозначноеИмя переклассифицируется как ИмяПакета. На более позднем этапе определяется, существует ли пакет с таким именем.
Если НеоднозначноеИмя является квалифицированным именем, состоящим из имени, ".", и Идентификатора, то имя слева от "." сначала переклассифицируется, так как само по себе является НеоднозначноеИмя. Затем есть выбор:
-
Если имя слева от "
." переклассифицируется как ИмяПакета, то:-
Если Идентификатор является допустимым ИдентификаторомТипа, и существует пакет, имя которого является именем слева от "
.", и этот пакет содержит объявление типа, имя которого совпадает с Идентификатором, то это НеоднозначноеИмя переклассифицируется как ИмяТипа. -
В противном случае это НеоднозначноеИмя переклассифицируется как ИмяПакета. На более позднем этапе определяется, существует ли пакет с таким именем.
-
-
Если имя слева от "
." переклассифицируется как ИмяТипа, то:-
Если Идентификатор является именем метода или поля типа, обозначаемого ИмяТипа, то это НеоднозначноеИмя переклассифицируется как ИмяВыражения.
-
В противном случае, если Идентификатор является допустимым ИдентификаторомТипа и является именем типа-члена типа, обозначаемого ИмяТипа, то это НеоднозначноеИмя переклассифицируется как ИмяТипа.
-
В противном случае возникает ошибка времени компиляции.
-
-
Если имя слева от "
." переклассифицируется как ИмяВыражения, то это НеоднозначноеИмя переклассифицируется как ИмяВыражения. На более позднем этапе определяется, существует ли член с именем Идентификатор.
Требование, чтобы потенциальное имя типа было "допустимым ИдентификаторомТипа", предотвращает обработку var и yield как имени типа. Обычно это избыточно, потому что правила для объявлений уже предотвращают введение типов с именами var и yield. Однако в некоторых случаях компилятор может найти бинарный класс с именем var или yield, и мы хотим чётко указать, что такие классы никогда не могут быть именованы. Самое простое решение — последовательно проверять наличие допустимого ИдентификатораТипа.
Пример 6.5.2-1. Переклассификация контекстуально неоднозначных имён
Рассмотрим следующий искусственный "код библиотеки":
package org.rpgpoet;
import java.util.Random;
public interface Music { Random[] wizards = new Random[4]; }
и затем рассмотрим этот пример кода в другом пакете:
package bazola;
class Gabriel {
static int n = org.rpgpoet.Music.wizards.length;
}
Прежде всего, имя org.rpgpoet.Music.wizards.length классифицируется как ИмяВыражения, потому что оно функционирует как ПостфиксноеВыражение. Поэтому каждое из имён:
org.rpgpoet.Music.wizards org.rpgpoet.Music org.rpgpoet org
изначально классифицируется как НеоднозначноеИмя. Затем они переклассифицируются:
-
Простое имя
orgпереклассифицируется как ИмяПакета (поскольку в области действия нет переменной или типа с именем org). -
Далее, предполагая, что нет класса или интерфейса с именем
rpgpoetни в одном модуле компиляции пакетаorg(и мы знаем, что такого класса или интерфейса нет, потому что пакетorgимеет подпакет с именемrpgpoet), квалифицированное имяorg.rpgpoetпереклассифицируется как ИмяПакета. -
Далее, поскольку пакет
org.rpgpoetимеет доступный (§6.6) тип интерфейса с именемMusic, квалифицированное имяorg.rpgpoet.Musicпереклассифицируется как ИмяТипа. -
Наконец, поскольку имя
org.rpgpoet.Musicявляется ИмяТипа, квалифицированное имяorg.rpgpoet.Music.wizardsпереклассифицируется как ИмяВыражения.
Имя модуля M, простое или квалифицированное, обозначает модуль (если таковой имеется) с этим именем.
Данный раздел не предписывает ошибку времени компиляции, если модуль с этим именем не наблюдается. Вместо этого директива requires в объявлении модуля (§7.7.1) выполняет собственную валидацию имени модуля, в то время как директивы exports и opens (§7.7.2) допускают имена несуществующих модулей.
Значение имени, классифицированного как ИмяПакета, определяется следующим образом.
Если имя пакета состоит из одного Идентификатора, то идентификатор должен встречаться в области действия ровно одного объявления верхнего уровня пакета с этим именем (§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 в метке переключения (§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 находится в области видимости на протяжении всего цикла for. Однако i не является эффективной final, поэтому использование имени i в теле лямбда-выражения является незаконным.
Если имя выражения имеет вид Q.Id, то Q уже классифицировано как имя пакета, имя типа или имя выражения.
Если Q является именем пакета, то возникает ошибка компиляции.
Если Q является именем типа, обозначающим тип класса, то:
-
Если нет ровно одного доступного члена (§6.6) типа класса, являющегося полем с именем
Id, то возникает ошибка компиляции. -
В противном случае, если единственное доступное поле-член не является статической переменной (то есть, оно не объявлено
static), то возникает ошибка компиляции. -
В противном случае, если статическая переменная объявлена
final, тоQ.Idобозначает значение статической переменной.Тип выражения
Q.Id— это объявленный тип статической переменной после преобразования захвата (§5.1.10).Если
Q.Idиспользуется в контексте, требующем переменную, а не значение, то возникает ошибка компиляции. -
В противном случае,
Q.Idобозначает статическую переменную.Тип выражения
Q.Id— это объявленный тип статической переменной после преобразования захвата (§5.1.10).Обратите внимание, что этот пункт охватывает использование
enumконстант (§8.9), так как они всегда имеют соответствующуюfinalстатическую переменную.
Если Q является именем типа, обозначающим тип интерфейса, то:
-
Если нет ровно одного доступного члена типа интерфейса, являющегося полем с именем
Id, то возникает ошибка компиляции. -
В противном случае,
Q.Idобозначает значение поля.Тип выражения
Q.Id— это объявленный тип поля после преобразования захвата (§5.1.10).Если
Q.Idиспользуется в контексте, требующем переменную, а не значение, то возникает ошибка компиляции.
Если Q — это имя выражения, пусть T будет типом выражения Q:
-
Если T — не ссылочный тип, то возникает ошибка компиляции.
-
Если нет ровно одного доступного члена типа T, являющегося полем с именем
Id, то возникает ошибка компиляции. -
В противном случае, если это поле является одним из следующих:
-
Поле типа интерфейса
-
Поле
finalтипа класса (которое может быть либо статической, либо экземпляра переменной) -
Поле
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и является членом пакета, не экспортированного модулем, то к классу или интерфейсу можно получить доступ из любого кода в том же модуле. -
Если класс или интерфейс верхнего уровня объявлен с доступом по умолчанию, то к нему можно получить доступ только изнутри пакета, в котором он объявлен.
Класс или интерфейс верхнего уровня, объявленный без модификатора доступа, неявно имеет доступ по умолчанию.
-
Член (класс, интерфейс, поле или метод) класса, интерфейса, параметра типа или типа ссылки, или конструктор класса, доступен только в том случае, если (i) класс, интерфейс, параметр типа или тип ссылки доступен, и (ii) член или конструктор объявлен с разрешением доступа:
-
Если член или конструктор объявлен
public, то доступ разрешен.Все члены интерфейсов, не имеющие модификаторов доступа, неявно являются
public. -
В противном случае, если член или конструктор объявлен
protected, то доступ разрешен только при выполнении одного из следующих условий:-
Доступ к члену или конструктору происходит изнутри пакета, содержащего класс, в котором объявлен
protectedчлен или конструктор. -
Доступ корректен, как описано в §6.6.2.
-
-
В противном случае, если член или конструктор объявлен с доступом по умолчанию, то доступ разрешен только при доступе изнутри пакета, в котором объявлен класс, интерфейс, параметр типа или тип ссылки.
Член класса или конструктор, объявленные без модификатора доступа, неявно имеют доступ по умолчанию.
-
В противном случае, член или конструктор объявлен
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. Доступ к полям, методам и конструкторам с доступом по умолчанию
Член класса или конструктор с доступом по умолчанию доступен во всем пакете, где он объявлен, и из любого другого пакета при условии, что пакет, в котором он объявлен, наблюдаем (§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. Доступ к классам с модификаторами доступа и без них
Если у класса нет модификатора доступа, доступ к объявлению класса ограничен пакетом, в котором он объявлен (§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 points;
public class Point {
public int x, y;
void move(int dx, int dy) { x += dx; y += dy; }
public void moveAlso(int dx, int dy) { move(dx, dy); }
}
то подкласс в другом пакете может объявить не связанный move метод с той же сигнатурой (§8.4.2) и типом возвращаемого значения. Поскольку исходный move метод недоступен из пакета morepoints, super не может быть использован:
package morepoints;
public class PlusPoint extends points.Point {
public void move(int dx, int dy) {
super.move(dx, dy); // compile-time error
moveAlso(dx, dy);
}
}
Поскольку move от Point не переопределяется move в PlusPoint, метод moveAlso в Point никогда не вызывает метод move в PlusPoint. Таким образом, если вы удалите вызов super.move из PlusPoint и выполните тестовую программу:
import points.Point;
import morepoints.PlusPoint;
class Test {
public static void main(String[] args) {
PlusPoint pp = new PlusPoint();
pp.move(1, 1);
}
}
она завершится нормально. Если move от Point переопределялся бы move в PlusPoint, то эта программа рекурсивно выполнялась бы бесконечно, пока не произошла ошибка StackOverflowError.
Пример 6.6-5. Доступ к private членам, методам и конструкторам
Член класса или конструктор с модификатором доступа private доступен только внутри тела класса верхнего уровня (§7.6), который содержит объявление члена или конструктора. Он не наследуется подклассами. В примере:
class Point {
Point() { setMasterID(); }
int x, y;
private int ID;
private static int masterID = 0;
private void setMasterID() { ID = masterID++; }
}
частные члены ID, masterID и setMasterID могут быть использованы только внутри тела класса Point. К ним нельзя получить доступ с использованием квалифицированных имен, выражений доступа к полям или выражений вызова метода за пределами тела объявления Point.
См. §8.8.10 для примера использования private конструктора.
Член или конструктор объекта может быть доступен извне пакета, в котором он объявлен, только кодом, ответственным за реализацию этого объекта.
Пусть C — класс, в котором объявлен защищённый член. Доступ разрешён только внутри тела подкласса 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.
Дополнительную информацию о доступе к защищённым членам можно найти в статье «Проверка доступа к защищённым членам в виртуальной машине Java» А. Коглио в журнале «Journal of Object Technology», октябрь 2005 г.
Пусть C — класс, в котором объявлен защищённый конструктор, и пусть S — самый внутренний класс, в объявлении которого используется этот защищённый конструктор. Тогда:
-
Если доступ осуществляется посредством вызова конструктора суперкласса
super(...)или квалифицированного вызова конструктора суперклассаE.super(...), гдеE— выражение Primary, то доступ разрешён. -
Если доступ осуществляется посредством выражения создания экземпляра анонимного класса
newC(...){...}или квалифицированного выражения создания экземпляра анонимного класса E.newC(...){...}, где E — выражение Primary, то доступ разрешён. -
Если доступ осуществляется посредством простого выражения создания экземпляра класса
newC(...)или квалифицированного выражения создания экземпляра класса E.newC(...), где E — выражение Primary, или выражения ссылки на метод C::new, где C — ClassType, то доступ запрещён. Защищённый конструктор может быть вызван выражением создания экземпляра класса (не анонимного) или выражением ссылки на метод только внутри пакета, в котором он определён.
Пример 6.6.2-1. Доступ к защищённым полям, методам и конструкторам
Рассмотрим пример, где пакет points объявляет:
package points;
public class Point {
protected int x, y;
void warp(threePoint.Point3d a) {
if (a.z > 0) // compile-time error: cannot access a.z
a.delta(this);
}
}
и пакет threePoint объявляет:
package threePoint;
import points.Point;
public class Point3d extends Point {
protected int z;
public void delta(Point p) {
p.x += this.x; // compile-time error: cannot access p.x
p.y += this.y; // compile-time error: cannot access p.y
}
public void delta3d(Point3d q) {
q.x += this.x;
q.y += this.y;
q.z += this.z;
}
}
В методе delta здесь возникает ошибка компиляции: он не может получить доступ к защищённым членам x и y своего параметра p, поскольку, хотя Point3d (класс, в котором происходят ссылки на поля x и y) является подклассом Point (класса, в котором объявлены x и y), он не участвует в реализации Point (типа параметра p). Метод delta3d может получить доступ к защищённым членам своего параметра q, поскольку класс Point3d является подклассом Point и участвует в реализации Point3d.
Метод delta может попытаться привести (§5.5, §15.16) свой параметр к типу Point3d, но это приведение может завершиться ошибкой, если класс p во время выполнения не будет Point3d.
Ошибка компиляции также возникает в методе warp: он не может получить доступ к защищённому члену z своего параметра a, поскольку, хотя класс Point (класс, в котором происходит ссылка на поле z) участвует в реализации Point3d (типа параметра a), он не является подклассом Point3d (класса, в котором объявлен z).
Каждый примитивный тип, именованный пакет, верхнеуровневый класс и верхнеуровневый интерфейс имеют полное имя:
-
Полное имя примитивного типа — это ключевое слово для этого примитивного типа, а именно
byte,short,char,int,long,float,doubleилиboolean. -
Полное имя именованного пакета, который не является подпакетом именованного пакета, — это его простое имя.
-
Полное имя именованного пакета, являющегося подпакетом другого именованного пакета, состоит из полного имени содержащего пакета, за которым следует "
.", а затем — простое (членное) имя подпакета. -
Полное имя верхнеуровневого класса или верхнеуровневого интерфейса, объявленного в безымянном пакете, — это простое имя класса или интерфейса.
-
Полное имя верхнеуровневого класса или верхнеуровневого интерфейса, объявленного в именованном пакете, состоит из полного имени пакета, за которым следует "
.", а затем — простое имя класса или интерфейса.
Каждый вложенный класс, вложенный интерфейс и тип массива могут иметь полное имя:
-
Вложенный класс или вложенный интерфейс
Mдругого класса или интерфейса C имеет полное имя тогда и только тогда, когда C имеет полное имя.В этом случае полное имя
Mсостоит из полного имени C, за которым следует ".", а затем — простое имяM. -
Тип массива имеет полное имя тогда и только тогда, когда его элементный тип имеет полное имя.
В этом случае полное имя типа массива состоит из полного имени компонента типа массива, за которым следует "
[]".
Локальный класс, локальный интерфейс или анонимный класс не имеют полного имени.
Каждый примитивный тип, именованный пакет, верхнеуровневый класс и верхнеуровневый интерфейс имеют каноническое имя:
-
Для каждого примитивного типа, именованного пакета, верхнеуровневого класса и верхнеуровневого интерфейса каноническое имя совпадает с полным именем.
Каждый вложенный класс, вложенный интерфейс и тип массива могут иметь каноническое имя:
-
Вложенный класс или вложенный интерфейс
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.