Spec-Zone.ru › Java Language Specification 17

Глава 6. Имена

Содержание

6.1. Объявления
6.2. Имена и идентификаторы
6.3. Область видимости объявления
6.3.1. Область видимости переменных шаблонов в выражениях
6.3.1.1. Оператор логического И &&
6.3.1.2. Оператор логического ИЛИ ||
6.3.1.3. Оператор логического НЕ !
6.3.1.4. Условный оператор ? :
6.3.1.5. Оператор сопоставления шаблонов instanceof
6.3.1.6. switch Выражения
6.3.1.7. Выражения в скобках
6.3.2. Область видимости переменных шаблонов в операторах
6.3.2.1. Блоки
6.3.2.2. if Операторы
6.3.2.3. while Операторы
6.3.2.4. do Операторы
6.3.2.5. for Операторы
6.3.2.6. switch Операторы
6.3.2.7. Операторы с метками
6.4. Перекрытие и затемнение
6.4.1. Перекрытие
6.4.2. Затемнение
6.5. Определение значения имени
6.5.1. Синтаксическая классификация имени в зависимости от контекста
6.5.2. Переклассификация контекстуально неоднозначных имён
6.5.3. Значение имён модулей и пакетов
6.5.3.1. Простые имена пакетов
6.5.3.2. Квалифицированные имена пакетов
6.5.4. Значение PackageOrTypeNames
6.5.4.1. Простые PackageOrTypeNames
6.5.4.2. Квалифицированные PackageOrTypeNames
6.5.5. Значение имён типов
6.5.5.1. Простые имена типов
6.5.5.2. Квалифицированные имена типов
6.5.6. Значение имён выражений
6.5.6.1. Простые имена выражений
6.5.6.2. Квалифицированные имена выражений
6.5.7. Значение имён методов
6.5.7.1. Простые имена методов
6.6. Управление доступом
6.6.1. Определение доступности
6.6.2. Подробности о protected доступе
6.6.2.1. Доступ к protected члену
6.6.2.2. Доступ к protected конструктору
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

6.1. Объявления

Объявление вводит сущность в программу и включает идентификатор (§3.8), который может использоваться в имени для ссылки на эту сущность. Идентификатор ограничен, чтобы избежать определенных контекстных ключевых слов, когда вводимая сущность является классом, интерфейсом или параметром типа.

Объявленная сущность является одной из следующих:

  • Модуль, объявленный в объявлении module (§7.7)

  • Пакет, объявленный в объявлении package (§7.4)

  • Импортированный класс или интерфейс, объявленный в объявлении импорта типа или объявлении импорта типа по требованию (§7.5.1, §7.5.2)

  • Импортированный член static, объявленный в объявлении одноэлементного статического импорта или объявлении статического импорта по требованию (§7.5.3, §7.5.4)

  • Класс, объявленный нормальным объявлением класса (§8.1), объявлением перечисления (§8.9) или объявлением записи (§8.10)

  • Интерфейс, объявленный нормальным объявлением интерфейса (§9.1) или объявлением аннотационного интерфейса (§9.6).

  • Параметр типа, объявленный как часть объявления обобщенного класса, интерфейса, метода или конструктора (§8.1.2, §9.1.2, §8.4.4, §8.8.4)

  • Член типа ссылки (§8.2, §9.2, §8.9.3, §9.6, §10.7), один из следующих:

    • Вложенный класс (§8.5, §9.5)

    • Вложенный интерфейс (§8.5, §9.5)

    • Поле, одно из следующих:

      • Поле, объявленное в классе (§8.3)

      • Поле, объявленное в интерфейсе (§9.3)

      • Неявное поле класса, соответствующее константе перечисления или компоненту записи

      • Поле length, которое неявным образом является членом каждого типа массива (§10.7)

    • Метод, один из следующих:

      • Метод (abstract или иначе) объявленный в классе (§8.4)

      • Метод (abstract или иначе) объявленный в интерфейсе (§9.4)

      • Неявный метод-аксессор, соответствующий компоненту записи

  • Константа перечисления (§8.9.1)

  • Компонент записи (§8.10.3)

  • Формальный параметр, один из следующих:

    • Формальный параметр метода класса или интерфейса (§8.4.1)

    • Формальный параметр конструктора класса (§8.8.1)

    • Формальный параметр лямбда-выражения (§15.27.1)

  • Параметр исключения обработчика исключений, объявленный в блоке catch оператора try (§14.20)

  • Локальная переменная, одна из следующих:

    • Локальная переменная, объявленная оператором объявления локальной переменной в блоке (§14.4.2)

    • Локальная переменная, объявленная оператором for или оператором try с ресурсами (§14.14, §14.20.3)

    • Локальная переменная, объявленная шаблоном (§14.30.1)

  • Вложенный класс или интерфейс (§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 необобщенных контекстов следующие:

  1. В директиве uses или provides в объявлении модуля (§7.7.1)

  2. В объявлении импорта одного типа (§7.5.1)

  3. Слева от . в объявлении импорта одного статического элемента (§7.5.3)

  4. Слева от . в объявлении импорта всех статических элементов (§7.5.4)

  5. В разделе permits объявления класса или интерфейса sealed (§8.1.6, §9.1.4).

  6. Слева от ( в объявлении конструктора (§8.8)

  7. После знака @ в аннотации (§9.7)

  8. Слева от .class в литерале класса (§15.8.2)

  9. Слева от .this в квалифицированном выражении this (§15.8.4)

  10. Слева от .super в квалифицированном выражении доступа к полю суперкласса (§15.11.2)

  11. Слева от .Идентификатор или .super.Идентификатор в квалифицированном выражении вызова метода (§15.12)

  12. Слева от .super:: в выражении ссылки на метод (§15.13)

  13. В квалифицированном имени выражения в постфиксном выражении или операторе try с ресурсами (§15.14.1, §14.20.3)

  14. В разделе throws метода или конструктора (§8.4.6, §8.8.5, §9.4)

  15. В объявлении параметра исключения (§14.20)

Первые двенадцать контекстов без дженериков соответствуют первым двенадцати синтаксическим контекстам для TypeName в §6.5.1. Тринадцатый контекст без дженериков — это контекст, где квалифицированное ExpressionName, такое как C.x, может включать TypeName C для обозначения доступа к статическому члену. Распространённое использование TypeName в этих тринадцати контекстах важно: оно указывает, что эти контексты включают менее первостепенное использование типа. В отличие от этого, четырнадцатый и пятнадцатый контексты без дженериков используют ClassType, указывая, что throws и catch разделы используют типы первостепенным образом, в соответствии, например, с объявлением полей. Описание этих двух контекстов как контекстов без дженериков обусловлено тем, что тип исключения не может быть параметризован (§8.1.2).

Обратите внимание, что производство ClassType допускает аннотации, поэтому можно аннотировать использование типа в throws или catch разделе, тогда как производство TypeName запрещает аннотации, поэтому нельзя аннотировать имя типа, например, в объявлении импорта одного типа.

Правила именования

Библиотеки классов Java SE Platform пытаются использовать, когда это возможно, имена, выбранные в соответствии с нижеприведенными правилами. Эти правила помогают сделать код более читаемым и избежать определённых типов конфликтов имён.

Мы рекомендуем использовать эти правила во всех программах, написанных на языке программирования Java. Однако этих правил не следует слепо придерживаться, если существующая традиционная практика диктует иное. Например, методы sin и cos класса java.lang.Math имеют математически традиционные имена, даже если эти имена методов нарушают рекомендуемые правила, потому что они короткие и не являются глаголами.

Имена пакетов и модулей

Программисты должны принимать меры, чтобы избежать возможности наличия двух опубликованных пакетов с одинаковым именем, выбирая уникальные имена пакетов для пакетов, которые широко распространены. Это позволяет легко и автоматически устанавливать и каталогизировать пакеты. В этом разделе описаны рекомендуемые правила для генерации таких уникальных имён пакетов. Реализации Java SE Platform рекомендуют обеспечивать автоматическую поддержку преобразования набора пакетов из локальных и обычных имён пакетов в формат уникальных имён, описанный здесь.

Если уникальные имена пакетов не используются, могут возникнуть конфликты имён пакетов далеко от места создания любого из конфликтующих пакетов. Это может создать ситуацию, которую сложно или невозможно разрешить пользователю или программисту. Классы ClassLoader и ModuleLayer могут использоваться для изоляции пакетов с одинаковым именем друг от друга в тех случаях, когда взаимодействие пакетов ограничено, но не таким образом, чтобы это было прозрачно для обычной программы.

Для формирования уникального имени пакета необходимо сначала иметь (или принадлежать организации, которая имеет) доменное имя в Интернете, например, oracle.com. Затем это имя необходимо развернуть по компонентам, чтобы в этом примере получить com.oracle, и использовать его в качестве префикса для имён пакетов, используя соглашение, разработанное внутри вашей организации для дальнейшего управления именами пакетов. Такое соглашение может предусматривать, что определённые компоненты имени пакета являются разделом, отделом, проектом, машиной или именем пользователя.

Пример 6.1-1. Уникальные имена пакетов

com.nighthacks.scrabble.dictionary
org.openjdk.compiler.source.tree
net.jcip.annotations
edu.cmu.cs.bovik.cheese
gov.whitehouse.socks.mousefinder

Первый компонент уникального имени пакета всегда записывается строчными латинскими буквами ASCII и должен быть одним из доменных имён верхнего уровня, таких как com, edu, gov, mil, net или org, или одним из двухбуквенных английских кодов, определяющих страны, как указано в Стандарте ISO 3166.

В некоторых случаях доменное имя может не быть допустимым именем пакета. Вот некоторые рекомендуемые соглашения для решения таких ситуаций:

  • Если доменное имя содержит дефис или любой другой специальный символ, не разрешённый в идентификаторе (§3.8), замените его на нижнее подчеркивание.

  • Если какой-либо из получившихся компонентов имени пакета является ключевым словом (§3.9), добавьте к нему нижнее подчеркивание.

  • Если какой-либо из получившихся компонентов имени пакета начинается с цифры или любого другого символа, который не разрешён в качестве первого символа идентификатора, добавьте нижнее подчеркивание перед этим компонентом.

Имя модуля должно соответствовать имени его основного экспортируемого пакета. Если у модуля нет такого пакета или по причинам совместимости с предыдущими версиями у него должно быть имя, которое не соответствует одному из его экспортируемых пакетов, то его имя всё равно должно начинаться с обращённой формы доменного имени в Интернете, с которым связан его автор.

Пример 6.1-2. Уникальные имена модулей

com.nighthacks.scrabble
org.openjdk.compiler
net.jcip.annotations

Первый компонент имени пакета или модуля не должен быть идентификатором java. Имена пакетов и модулей, начинающиеся с идентификатора java, зарезервированы для пакетов и модулей Java SE Platform.

Имя пакета или модуля не должно подразумевать, где пакет или модуль хранятся в Интернете. Например, пакет с именем edu.cmu.cs.bovik.cheese не обязательно доступен по адресу cmu.edu или cs.cmu.edu или bovik.cs.cmu.edu. Предлагаемое соглашение для генерации уникальных имён пакетов и модулей просто позволяет использовать соглашение об именовании пакетов и модулей в существующей широко известной системе регистрации уникальных имён вместо создания отдельной системы регистрации для имён пакетов и модулей.

Имена классов и интерфейсов

Имена классов должны быть описательными существительными или словосочетаниями, не слишком длинными, с использованием смешанного регистра, где первая буква каждого слова заглавная.

Пример 6.1-3. Описательные имена классов

ClassLoader
SecurityManager
Thread
Dictionary
BufferedInputStream

Аналогично, имена интерфейсов должны быть короткими и описательными, не чрезмерно длинными, с использованием смешанного регистра, при этом первая буква каждого слова должна быть заглавной. Имя может быть описательным существительным или существительным во фразе, что уместно, когда интерфейс используется как абстрактный суперкласс, например, интерфейсы java.io.DataInput и java.io.DataOutput; или это может быть прилагательное, описывающее поведение, как для интерфейсов Runnable и Cloneable.

Имена переменных типа

Имена переменных типа должны быть емкими (если возможно, одной буквой) и выразительными, и не должны включать строчные буквы. Это упрощает различение параметров типа от обычных классов и интерфейсов.

Классы и интерфейсы контейнеров должны использовать имя E для типа их элементов. Карты должны использовать K для типа своих ключей и V для типа своих значений. Имя X должно использоваться для произвольных типов исключений. Мы используем T для типа, когда нет более специфической информации о типе для его различения. (Это часто встречается в методах дженериков.)

Если есть несколько параметров типа, обозначающих произвольные типы, следует использовать буквы, близкие к T в алфавите, такие как S. В качестве альтернативы допустимо использовать числовые индексы (например, T1, T2) для различения различных переменных типа. В таких случаях все переменные с одинаковым префиксом должны иметь индекс.

Если метод дженерика появляется внутри дженерик-класса, рекомендуется избегать использования одинаковых имён для параметров типа метода и класса, чтобы избежать путаницы. То же самое относится к вложенным дженерик-классам.

Пример 6.1-4. Стандартные имена переменных типа


public class HashSet<E> extends AbstractSet<E> { ... }
public class HashMap<K,V> extends AbstractMap<K,V> { ... }
public class ThreadLocal<T> { ... }
public interface Functor<T, X extends Throwable> {
    T eval() throws X;
}


Когда параметры типа не подходят удобно ни под одну из упомянутых категорий, имена должны выбираться максимально осмысленными в рамках одной буквы. Указанные выше имена (E, K, V, X, T) не должны использоваться для параметров типа, которые не попадают в обозначенные категории.

Имена методов

Имена методов должны быть глаголами или глагольными фразами, со смешанным регистром, первой буквой в нижнем регистре и первой буквой каждого последующего слова заглавной. Вот некоторые дополнительные конкретные соглашения для имён методов:

  • Методы для получения и установки атрибута, который можно рассматривать как переменную V должны называться getV и setV. Примером являются методы getPriority и setPriority класса Thread.

  • Метод, возвращающий длину чего-либо, должен называться length, как в классе String.

  • Метод, проверяющий булево условие V об объекте, должен называться isV. Примером является метод isInterrupted класса Thread.

  • Метод, преобразующий свой объект в определённый формат F, должен называться toF. Примерами являются метод toString класса Object и методы toLocaleString и toGMTString класса java.util.Date.

По возможности и уместно, следует основывать имена методов в новом классе на именах методов в существующем похожем классе, особенно классе из API платформы Java SE, это облегчит их использование.

Имена полей

Имена полей, которые не являются final, должны быть со смешанным регистром, с первой буквой в нижнем регистре и первой буквой последующих слов заглавной. Обратите внимание, что хорошо спроектированные классы содержат очень мало public или protected полей, за исключением полей, которые являются константами (static final поля).

Поля должны иметь имена, являющиеся существительными, существительными во фразах или аббревиатурами существительных.

Примеры такого соглашения — поля buf, pos и count класса java.io.ByteArrayInputStream и поле bytesTransferred класса java.io.InterruptedIOException.

Имена констант

Имена констант в интерфейсах должны быть, и final переменные классов могут быть, последовательностью одного или более слов, аббревиатур или сокращений, все заглавными буквами, с компонентами, разделенными символами подчеркивания "_". Имена констант должны быть описательными и не излишне сокращенными. Согласно соглашению, они могут быть любыми подходящими частями речи.

Примеры имён констант включают MIN_VALUE, MAX_VALUE, MIN_RADIX и MAX_RADIX класса Character.

Группа констант, представляющих альтернативные значения набора, или, реже, маскирующие биты в целочисленном значении, иногда удобно задавать с общим префиксом в качестве имени.

Например:

interface ProcessStates {
    int PS_RUNNING   = 0;
    int PS_SUSPENDED = 1;
}

Имена локальных переменных и параметров

Имена локальных переменных и параметров должны быть короткими, но информативными. Часто это короткие последовательности строчных букв, которые не являются словами, такие как:

  • Аббревиатуры, то есть первая буква серии слов, как в cp для переменной, хранящей ссылку на ColoredPoint

  • Сокращения, как в buf, хранящей указатель на буфер какого-либо рода

  • Мнемонические термины, организованные каким-либо образом для облегчения запоминания и понимания, обычно путём использования набора локальных переменных с согласованными именами, соответствующими именам параметров широко используемых классов. Например:

    • in и out, всякий раз, когда задействованы какие-либо входные и выходные данные, по аналогии с полями System

    • off и len, всякий раз, когда задействованы смещение и длина, по аналогии с параметрами методов read и write интерфейсов DataInput и DataOutput из java.io

Однобуквенных имён локальных переменных или параметров следует избегать, за исключением временных и циклических переменных или переменных, хранящих незаметное значение типа. Стандартные однобуквенные имена:

  • b для byte

  • c для char

  • d для double

  • e для Exception

  • f для float

  • i, j и k для int

  • l для long

  • o для Object

  • s для String

  • v для произвольного значения некоторого типа

Имена локальных переменных или параметров, состоящие только из двух или трёх строчных букв, не должны совпадать с начальными кодами стран и доменными именами, которые являются первым компонентом уникальных имён пакетов.

6.2. Имена и идентификаторы

Имя используется для ссылки на сущность, объявленную в программе.

Существуют два вида имён: простые имена и квалифицированные имена.

Простым именем является единственный идентификатор.

Квалифицированное имя состоит из имени, токена "." и идентификатора.

При определении значения имени (§6.5) учитывается контекст, в котором это имя встречается. Правила §6.5 различают контексты, где имя должно обозначать (ссылаться) на пакет (§6.5.3); класс, интерфейс или параметр типа (§6.5.5); переменную или значение в выражении (§6.5.6); или метод (§6.5.7).

У пакетов, классов, интерфейсов и параметров типа есть члены, к которым можно получить доступ с помощью квалифицированных имён. Для ознакомления с квалифицированными именами и определением значений имён, см. описания членства в §4.4, §4.5.2, §4.8, §4.9, §7.1, §8.2, §9.2 и §10.7.

Не все идентификаторы в программе являются частью имени. Идентификаторы также используются в следующих ситуациях:

  • В объявлениях (§6.1), где идентификатор может использоваться для указания имени, под которым будет известна объявленная сущность.

  • В качестве меток в помеченных операторах (§14.7) и в операторах break и continue (§14.15, §14.16), которые ссылаются на метки операторов.

    Идентификаторы, используемые в помеченных операторах и связанных с ними операторах break и continue, полностью отделены от тех, которые используются в объявлениях.

  • В выражениях доступа к полям (§15.11), где идентификатор стоит после токена "." для указания члена объекта, обозначенного выражением перед токеном ".", или объекта, обозначаемого super или TypeName.super перед токеном ".".

  • В некоторых выражениях вызова метода (§15.12), где идентификатор находится после токена "." и перед токеном "(" для указания метода, который должен быть вызван для объекта, обозначенного выражением перед токеном ".", или типа, обозначенного TypeName перед токеном ".", или объекта, обозначенного super или TypeName.super перед токеном ".".

  • В некоторых выражениях ссылки на метод (§15.13), где идентификатор находится после токена "::" для указания метода объекта, обозначенного выражением перед токеном "::", или типа, обозначенного TypeName перед токеном "::", или объекта, обозначаемого super или TypeName.super перед токеном "::".

  • В выражениях создания экземпляров класса с квалификацией (§15.9), где идентификатор находится справа от токена new для обозначения типа, являющегося членом времени компиляции типа выражения, предшествующего токену new.

  • В парах имя-значение аннотаций (§9.7.1), для обозначения элемента соответствующего интерфейса аннотации.

В этой программе:

class Test {
    public static void main(String[] args) {
        Class c = System.out.getClass();
        System.out.println(c.toString().length() +
                           args[0].length() + args.length);
    }
}

идентификаторы Test, main, и первые вхождения args и c не являются именами. Скорее, они являются идентификаторами, используемыми в объявлениях для указания имён объявленных сущностей. В примере присутствуют имена String, Class, System.out.getClass, System.out.println, c.toString, args и args.length.

Вхождение length в args.length является именем, потому что args.length — квалифицированное имя (§6.5.6.2), а не выражение доступа к полю (§15.11). Выражение доступа к полю, а также выражение вызова метода, выражение ссылки на метод и выражение создания экземпляра класса с квалификацией используют идентификатор, а не имя для обозначения интересующего члена. Таким образом, вхождение length в args[0].length() — не имя, а идентификатор, используемый в выражении вызова метода.

Можно задаться вопросом, почему в этих выражениях используется идентификатор, а не простое имя, которое, в конце концов, просто идентификатор. Причина в том, что простое имя выражения определяется в терминах лексической среды; то есть, простое имя выражения должно находиться в области видимости объявления переменной (§6.5.6.1). С другой стороны, доступ к полям, квалифицированный вызов метода, ссылки на методы и создание экземпляров класса с квалификацией относятся к членам, имена которых не находятся в лексической среде. По определению, такие имена связываются только в контексте, предоставляемом Primary выражения доступа к полю, выражения вызова метода, выражения ссылки на метод или выражения создания экземпляра класса; или Qualifier выражения доступа к полю, выражения вызова метода или выражения ссылки на метод; и так далее. Поэтому мы обозначаем таких членов идентификаторами, а не простыми именами.

Чтобы еще больше усложнить ситуацию, выражение доступа к полю — не единственный способ обозначить поле объекта. По причинам разбора, квалифицированное имя используется для обозначения поля в области видимости переменной. (Сама переменная обозначается простым именем, упомянутым выше.) Необходимо, чтобы контроль доступа (§6.6) применялся к обоим обозначениям поля.

6.3. Область действия объявления

Область действия объявления — это область программы, в пределах которой к сущности, объявленной этим объявлением, можно обратиться с помощью простого имени, при условии, что она не затеняется (§6.4.1).

Считается, что объявление находится в области действия в определенной точке программы тогда и только тогда, когда область действия объявления включает эту точку.

Область действия объявления наблюдаемого верхнего уровня пакета (§7.4.3) охватывает все наблюдаемые единицы компиляции, связанные с модулями, для которых пакет имеет уникальную видимость (§7.4.3).

Объявление пакета, который не наблюдаем, никогда не находится в области действия.

Объявление подпакета никогда не находится в области действия.

Пакет java всегда находится в области действия.

Область действия класса или интерфейса, импортированного объявлением импорта одного типа (§7.5.1) или объявлением импорта по требованию типа (§7.5.2), — это объявление модуля (§7.7) и все объявления классов и интерфейсов (§8.1, §9.1) в единице компиляции, в которой появляется объявление import, а также все аннотации к объявлениям модуля или пакета этой единицы компиляции.

Область действия члена, импортированного объявлением импорта одного статического члена (§7.5.3) или объявлением импорта статических членов по требованию (§7.5.4), — это объявление модуля и все объявления классов и интерфейсов в единице компиляции, в которой находится объявление import, а также все аннотации к объявлениям модуля или пакета этой единицы компиляции.

Область действия верхнего уровня класса или интерфейса (§7.6) — это все объявления классов и интерфейсов в пакете, в котором объявлен верхнеуровневый класс или интерфейс.

Область действия объявления члена m, объявленного в классе или интерфейсе C или унаследованного от него (§8.2, §9.2) — это весь текст C, включая объявления вложенных классов или интерфейсов.

Область действия формального параметра метода (§8.4.1), конструктора (§8.8.1) или лямбда-выражения (§15.27) — это весь текст метода, конструктора или лямбда-выражения.

Область действия параметра типа класса (§8.1.2) — это раздел параметров типа объявления класса, раздел параметров типа любого суперкласса или суперинтерфейса объявления класса и тело класса. Если класс является записывающим классом (§8.10), то область действия параметра типа дополнительно включает заголовок объявления записи (§8.10.1).

Область действия параметра типа интерфейса (§9.1.2) — это раздел параметров типа объявления интерфейса, раздел параметров типа любого суперинтерфейса объявления интерфейса и тело интерфейса.

Область действия параметра типа метода (§8.4.4) — это все объявление метода, включая раздел параметров типа, но не включая модификаторы метода.

Область действия параметра типа конструктора (§8.8.4) — это все объявление конструктора, включая раздел параметров типа, но не включая модификаторы конструктора.

Область действия объявления вложенного класса или интерфейса, непосредственно заключенного в блок (§14.2), — это остальная часть непосредственно содержащего блока, включая само объявление вложенного класса или интерфейса.

Область действия объявления вложенного класса или интерфейса, непосредственно заключенного в группу инструкций switch (§14.11), — это остальная часть непосредственно содержащей группы инструкций switch, включая само объявление вложенного класса или интерфейса.

Область действия локальной переменной, объявленной в блоке с помощью оператора объявления локальной переменной (§14.4.2), — это остальная часть блока, начиная с инициализатора объявления и включая все последующие объявления в операторе объявления локальной переменной.

Область действия локальной переменной, объявленной в части ForInit оператора цикла basic for (§14.14.1) включает в себя:

  • Собственный инициализатор

  • Любые последующие объявления справа в части ForInit оператора цикла

  • Части Expression и ForUpdate оператора цикла

  • Содержащуюся инструкцию Statement

Область действия локальной переменной, объявленной в заголовке оператора цикла enhanced for (§14.14.2), — это содержащаяся инструкция Statement.

Область действия локальной переменной, объявленной в спецификации ресурсов оператора try-with-resources (§14.20.3) — это область от объявления вправо до остальной части спецификации ресурсов и всего блока try, связанного с оператором try-with-resources.

Перевод оператора try-with-resources подразумевает вышеупомянутое правило.

Область действия параметра обработчика исключения, объявленного в блоке catch оператора try (§14.20) — это весь блок, связанный с catch.

Пример 6.3-1. Область действия объявлений классов

Эти правила подразумевают, что объявления типов классов и интерфейсов не обязательно должны появляться до использования этих типов. В следующей программе использование PointList в классе Point допустимо, потому что область действия объявления класса PointList включает как класс Point, так и класс PointList, а также любые другие объявления классов или интерфейсов в других единицах компиляции пакета points.

package points;
class Point {
    int x, y;
    PointList list;
    Point next;
}

class PointList {
    Point first;
}

Пример 6.3-2. Область действия объявления локальных переменных

Следующая программа приводит к ошибке компиляции, потому что инициализация локальной переменной x находится в области действия объявления локальной переменной x, но локальная переменная x еще не имеет значения и не может быть использована. Поле x имеет значение 0 (присвоено при инициализации Test1), но является «ложным следом», так как оно затеняется (§6.4.1) локальной переменной x.

class Test1 {
    static int x;
    public static void main(String[] args) {
        int x = x;
    }
}

Следующая программа компилируется:

class Test2 {
    static int x;
    public static void main(String[] args) {
        int x = (x=2)*2;
        System.out.println(x);
    }
}

потому что локальной переменной x определенно присваивается значение (§16 (Definite Assignment)) перед её использованием. Она выводит:

4

В следующей программе инициализатор для three может правильно ссылаться на переменную two, объявленную в более раннем операторе, и вызов метода в следующей строке может правильно ссылаться на переменную three, объявленную ранее в блоке.

class Test3 {
    public static void main(String[] args) {
        System.out.print("2+1=");
        int two = 2, three = two + 1;
        System.out.println(three);
    }
}

Эта программа выводит:

2+1=3

Область действия объявления переменной шаблона (то есть локальной переменной, объявленной шаблоном) — это часть программы, которая может быть выполнена после успешного сопоставления значения с шаблоном (§14.30.2). Она определяется путём рассмотрения точек программы, где переменная шаблона определённо сопоставляется в области, начинающейся с шаблона, который объявляет переменную шаблона.

Остальная часть данного раздела посвящена точному толкованию слов "определённо сопоставляется". Анализ учитывает структуру операторов и выражений, с особым вниманием к операторам логических выражений и определённым формам операторов.

Видно, что область действия объявления переменной шаблона — это понятие, зависящее от потока, аналогичное определенному присваиванию (§16 (Определённое присваивание)). Правила, определённые в остальной части этого раздела, умышленно имеют сходную форму с правилами определённого присваивания.

Анализ опирается на технический термин «введённый», который имеет следующий вид:

  • переменная шаблона вводится выражением при истинном значении

  • переменная шаблона вводится выражением при ложном значении

  • переменная шаблона вводится оператором

Простейший пример — переменная шаблона s вводится выражением a instanceof String s при истинном значении. Другими словами, если значение выражения равно true, то сопоставление шаблонов должно было быть успешным, а значит, переменная шаблона должна была получить значение.

В противоположность этому, переменная шаблона t вводится выражением !(b instanceof Integer t) при ложном значении. Это связано с тем, что сопоставление шаблонов могло быть успешным только в том случае, если значение выражения равно false.

6.3.1. Область действия переменных шаблонов в выражениях

Только определённые виды логических выражений участвуют во введении переменных шаблонов и определении мест, где эти переменные гарантированно сопоставлены. Если выражение не является выражением с оператором «и», выражением с оператором «или», выражением логического отрицания, условным выражением, выражением instanceof, выражением switch или выражением в скобках, то правила области действия не применяются.

6.3.1.1. Оператор логического «и» &&

Следующие правила применяются к выражению с оператором «и» a && b (§15.23):

  • Переменная шаблона, введённая с помощью a при истинном значении, гарантированно сопоставляется в b.

    Ошибка компиляции, если любая переменная шаблона, введённая с помощью a при истинном значении, уже находится в области действия в b.

  • Переменная шаблона вводится с помощью a && b при истинном значении тогда и только тогда, когда либо (i) она вводится с помощью a при истинном значении, либо (ii) она вводится с помощью b при истинном значении.

Следует отметить, что нет правила для введения переменной шаблона с помощью a && b при ложном значении. Это связано с тем, что в момент компиляции невозможно определить, какой операнд примет значение false.

Ошибка компиляции, если выполняются какие-либо из следующих условий:

  • Переменная шаблона вводится как (i) с помощью a при истинном значении, так и (ii) с помощью b при истинном значении.

  • Переменная шаблона вводится как (i) с помощью a при ложном значении, так и (ii) с помощью b при ложном значении.

Эти два случая ошибок исключают возможность объявления переменной шаблона с одинаковым именем для обоих операндов оператора &&. Например, рассмотрим проблемное выражение (a instanceof String s) && (b instanceof String s). Первый случай ошибки покрывает ситуацию, когда всё выражение вычисляет значение true, где (если бы код был корректным) потребовалось бы инициализировать две объявления переменной шаблона s, учитывая, что как левый, так и правый операнды вычислили значение true. Поскольку нет возможности отличить две переменные с именем s в остальной части программы, всё выражение считается ошибочным. Второй случай ошибки покрывает противоположную ситуацию, когда всё выражение вычисляет значение false.

6.3.1.2. Оператор логического «или» ||

Следующие правила применяются к выражению с оператором «или» a || b (§15.24):

  • Переменная шаблона, введённая с помощью a при ложном значении, гарантированно сопоставляется в b.

    Ошибка компиляции, если любая переменная шаблона, введённая с помощью a при ложном значении, уже находится в области действия в b.

  • Переменная шаблона вводится с помощью a || b при ложном значении тогда и только тогда, когда либо (i) она вводится с помощью a при ложном значении, либо (ii) она вводится с помощью b при ложном значении.

Следует отметить, что нет правила для введения переменной шаблона с помощью a || b при истинном значении. Это связано с тем, что в момент компиляции невозможно определить, какой операнд примет значение true.

Ошибка компиляции, если выполняются какие-либо из следующих условий:

  • Переменная шаблона вводится как (i) с помощью a при истинном значении, так и (ii) с помощью b при истинном значении.

  • Переменная шаблона вводится как (i) с помощью a при ложном значении, так и (ii) с помощью b при ложном значении.

Эти два случая ошибок исключают возможность объявления переменной шаблона с одинаковым именем для обоих операндов оператора ||. Например, рассмотрим проблемное выражение (a instanceof String s) || (b instanceof String s). Первый случай ошибки покрывает ситуацию, когда всё выражение вычисляет значение true, где (если бы код был корректным) была бы инициализирована ровно одна декларация переменной шаблона s в зависимости от того, какой операнд вычислил значение true. Поскольку в момент компиляции невозможно определить, какой операнд вычислит значение true, а следовательно, какая декларация s будет инициализирована, всё выражение считается ошибочным. Второй случай ошибки покрывает противоположную ситуацию, когда всё выражение вычисляет значение false.

6.3.1.3. Оператор логического отрицания !

Следующие правила применяются к выражению логического отрицания !a (§15.15.6):

  • Переменная шаблона вводится с помощью !a при истинном значении тогда и только тогда, когда она вводится с помощью a при ложном значении.

  • Переменная шаблона вводится с помощью !a при ложном значении тогда и только тогда, когда она вводится с помощью a при истинном значении.

6.3.1.4. Условный оператор ? :

Следующие правила применяются к условному выражению a ? b : c (§15.25):

  • Переменная шаблона, введённая с помощью a при истинном значении, гарантированно сопоставляется в b.

    Ошибка компиляции, если любая переменная шаблона, введённая с помощью a при истинном значении, уже находится в области действия в b.

  • Переменная шаблона, введённая с помощью a при ложном значении, гарантированно сопоставляется в c.

    Ошибка компиляции, если любая переменная шаблона, введённая с помощью a при ложном значении, уже находится в области действия в c.

Следует отметить, что нет правил для введения переменной шаблона с помощью a ? b : c при истинном или ложном значении. Это связано с тем, что в момент компиляции невозможно определить, вычислит ли операнд a значение true.

Ошибка компиляции, если выполняются какие-либо из следующих условий:

  • Переменная шаблона вводится как (i) с помощью a при истинном значении, так и (ii) с помощью c при истинном значении.

  • Переменная шаблона вводится как (i) с помощью a при истинном значении, так и (ii) с помощью c при ложном значении.

  • Переменная шаблона вводится как (i) с помощью a при ложном значении, так и (ii) с помощью b при истинном значении.

  • Переменная шаблона вводится как (i) с помощью a при ложном значении, так и (ii) с помощью b при ложном значении.

  • Переменная шаблона вводится как (i) с помощью b при истинном значении, так и (ii) с помощью c при истинном значении.

  • Переменная шаблона вводится как (i) с помощью b при ложном значении, так и (ii) с помощью c при ложном значении.

Эти случаи ошибок аналогичны аналогичным случаям ошибок для операторов && и ||. Они исключают запутанные случаи, когда могут возникнуть несколько деклараций одной и той же переменной шаблона в операндах оператора ? :.

6.3.1.5. Оператор соответствия шаблону instanceof

Следующие правила применяются к выражению instanceof с операндом-шаблоном, a instanceof p (§15.20.2):

  • Переменная шаблона вводится a instanceof p, если истинно, что она объявлена шаблоном p. Правила определения переменных шаблона, объявляемых шаблоном, приведены в §14.30.1.

    Если переменная шаблона, введённая a instanceof p при истинности, уже находится в области видимости в выражении instanceof, это является ошибкой компиляции.

Переменная шаблона не может затенять другую локальную переменную (§6.4).

Следует отметить, что нет правила введения переменной шаблона a instanceof p при ложности.

6.3.1.6. Выражения switch

Следующее правило применяется к выражению switch (§15.28):

  • Переменная шаблона, введённая оператором S в группе операторов со switch-меткой (§14.11.1), гарантированно соответствует всем операторам, следующим за S, если таковые имеются, в группе операторов со switch-меткой.

6.3.1.7. Выражения в скобках

Следующие правила применяются к выражению в скобках (a) (§15.8.5):

  • Переменная шаблона вводится (a), если истинно, что она вводится a, когда истинно.

  • Переменная шаблона вводится (a), если ложно, что она вводится a, когда ложно.

6.3.2. Область действия переменных шаблонов в операторах

Лишь несколько типов операторов играют существенную роль в определении области действия переменных шаблонов.

Где оператор 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));
    ...
}

6.3.2.1. Блоки

Следующее правило применяется к оператору блока S, содержащемуся в блоке (§14.2), который не является блоком switch (§14.11.1):

  • Переменная шаблона, введённая оператором S, определённо сопоставляется со всеми операторами блока, следующими за оператором S, если таковые имеются.

6.3.2.2. Операторы if

Следующие правила применяются к оператору if (e) S (§14.9.1):

  • Переменная шаблона, введённая e при истинном значении, определённо сопоставляется с оператором S.

    Если любая переменная шаблона, введённая e при истинном значении, уже находится в области действия оператора S, это ошибка компиляции.

  • Переменная шаблона вводится if (e) S тогда и только тогда, когда (i) она вводится e при ложном значении и (ii) S не может завершиться нормально.

    Если любая переменная шаблона, введённая if (e) S, уже находится в области действия оператора if, это ошибка компиляции.

Правило о введении переменной шаблона оператором if-then опирается на понятие «не может завершиться нормально» (§14.22), которое, в свою очередь, опирается на понятие константного выражения (§15.29). Это означает, что вычисление области действия переменной шаблона может потребовать определения, ссылается ли простое имя или квалифицированное имя в форме TypeName . Identifier на константную переменную. Поскольку переменные шаблонов никогда не могут ссылаться на константную переменную, цикличности нет.

Следующие правила применяются к оператору if (e) S else T (§14.9.2):

  • Переменная шаблона, введённая e при истинном значении, определённо сопоставляется с оператором S.

    Если любая переменная шаблона, введённая e при истинном значении, уже находится в области действия оператора S, это ошибка компиляции.

  • Переменная шаблона, введённая e при ложном значении, определённо сопоставляется с оператором T.

    Если любая переменная шаблона, введённая e при ложном значении, уже находится в области действия оператора T, это ошибка компиляции.

  • Переменная шаблона вводится if (e) S else T тогда и только тогда, когда выполняется одно из следующих условий:

    • Она вводится e при истинном значении, S может завершиться нормально, а T не может завершиться нормально; или

    • Она вводится e при ложном значении, S не может завершиться нормально, а T может завершиться нормально.

    Если любая переменная шаблона, введённая if (e) S else T, уже находится в области действия оператора if, это ошибка компиляции.

Эти правила отражают поточную природу области действия переменных шаблонов. Например, в следующем операторе:

if (e instanceof String s) {
    counter += s.length();
} else {
    System.out.println(e);  // s not in scope
}

Переменная шаблона s вводится выражением instanceof и находится в области действия первого вложенного оператора (оператор присваивания в блоке then), но не находится в области действия второго вложенного оператора (оператор выражения в блоке else).

Кроме того, в сочетании с обработкой булевых выражений, область действия переменных шаблонов устойчива к изменениям кода, которые используют известные булевы логические эквивалентности. Например, предыдущий код можно переписать как:

if (!(e instanceof String s)) {
    System.out.println(e);  // s not in scope
} else {
    counter += s.length();
}

Код можно даже переписать следующим образом, хотя двойное использование оператора ! не обязательно рекомендуется:

if (!!(e instanceof String s)) {
    counter += s.length();
} else {
    System.out.println(e);  // s not in scope
}

6.3.2.3. Операторы while

Следующие правила применяются к оператору while (e) S (§14.12):

  • Переменная шаблона, введённая e при истинном значении, определённо сопоставляется с оператором S.

    Если любая переменная шаблона, введённая e при истинном значении, уже находится в области действия оператора S, это ошибка компиляции.

  • Переменная шаблона вводится while (e) S тогда и только тогда, когда (i) она вводится e при ложном значении и (ii) S не содержит достижимого оператора break, целевой оператор break которого содержит S (§14.15).

    Если любая переменная шаблона, введённая while (e) S, уже находится в области действия оператора while, это ошибка компиляции.

6.3.2.4. Операторы do

Следующие правила применяются к оператору do S while (e) (§14.13):

  • Переменная шаблона вводится do S while (e) тогда и только тогда, когда (i) она вводится e при ложном значении и (ii) S не содержит достижимого оператора break, целевой оператор break которого содержит S (§14.15).

    Если любая переменная шаблона, введённая do S while (e), уже находится в области действия оператора do, это ошибка компиляции.

6.3.2.5. for Операторы цикла for

Следующие правила применяются к оператору цикла for (§14.14.1):

  • Переменная образца, введённая выражением условия при истинности, однозначно сопоставляется как в части инкрементации, так и в содержащем операторе.

    Если какая-либо переменная образца, введённая выражением условия при истинности, уже находится в области видимости в части инкрементации содержащего оператора, возникает ошибка компиляции.

  • Переменная образца вводится оператором цикла for при ложности условия, если (i) она вводится выражением условия при ложности и (ii) содержащий оператор S не содержит достижимый оператор break, целевой точкой которого является S (§14.15).

    Если какая-либо переменная образца, введённая оператором цикла for, уже находится в области видимости в операторе for, возникает ошибка компиляции.

Расширенный оператор цикла for (§14.14.2) определяется переводом в оператор цикла for, поэтому для него нет необходимости в особых правилах.

6.3.2.6. switch Операторы switch

Следующее правило применяется к оператору switch (§14.11):

  • Переменная образца, введённая помеченным оператором S, содержащимся в группе операторов блока switch (§14.11.1), однозначно сопоставляется со всеми операторами, следующими за S (если таковые имеются) в группе операторов блока switch.

6.3.2.7. Помеченные операторы

Следующее правило применяется к помеченному оператору (§14.7):

  • Переменная образца вводится помеченным оператором, если она вводится непосредственно содержащимся оператором Оператор.

6.4. Затенение и затемнение

Локальная переменная (§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");
    }
}


6.4.1. Затенение

Некоторые объявления могут быть затенены в части своего области видимости другим объявлением с тем же именем, в этом случае простое имя не может быть использовано для ссылки на объявленный объект.

Затенение отличается от скрытия (§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, объявления:

  • любой статический тип с именем n, импортированный объявлением импорта статических элементов по требованию в c;

  • любой тип верхнего уровня (§7.6) с именем n, объявленный в другой единице компиляции (§7.3) p;

  • любой тип с именем n, импортированный объявлением импорта типа по требованию (§7.5.2) в c.

Пример 6.4.1-1. Затенение объявления поля локальной переменной

class Test {
    static int x = 1;
    public static void main(String[] args) {
        int x = 0;
        System.out.print("x=" + x);
        System.out.println(", Test.x=" + Test.x);
    }
}

Эта программа выводит:

x=0, Test.x=1

Эта программа объявляет:

  • класс Test

  • переменную класса (static) x, которая является членом класса Test

  • метод класса main, который является членом класса Test

  • параметр args метода main

  • локальную переменную x метода main

Поскольку область видимости переменной класса включает весь текст класса (§8.2), переменная класса x обычно доступна во всем тексте метода main. Однако в этом примере переменная класса x затеняется в теле метода main объявлением локальной переменной x.

Локальная переменная имеет область видимости остальной части блока, в котором она объявлена (§6.3); в данном случае это остальная часть тела метода main, а именно его инициализатор "0" и вызовы System.out.print и System.out.println.

Это означает, что:

  • выражение x в вызове print относится к (обозначает) значению локальной переменной x.

  • вызов println использует квалифицированное имя (§6.6) Test.x, которое использует имя типа класса Test для доступа к переменной класса x, так как объявление Test.x затенено в данной точке и к нему нельзя обратиться по его простому имени.

Ключевое слово this также может быть использовано для доступа к затененному полю x, используя форму this.x. Действительно, этот idiom обычно встречается в конструкторах (§8.8):

class Pair {
    Object first, second;
    public Pair(Object first, Object second) {
        this.first = first;
        this.second = second;
    }
}

Здесь конструктор принимает параметры с теми же именами, что и поля, которые необходимо инициализировать. Это проще, чем придумывать разные имена для параметров и не слишком запутанно в этом стилизованном контексте. Однако в целом считается плохим стилем иметь локальные переменные с теми же именами, что и поля.


Пример 6.4.1-2. Затенение объявления типа другим объявлением типа

import java.util.*;
class Vector {
    int val[] = { 1 , 2 };
}

class Test {
    public static void main(String[] args) {
        Vector v = new Vector();
        System.out.println(v.val[0]);
    }
}

Программа компилируется и выводит:

1

используя объявленный здесь класс Vector в приоритете перед обобщенным классом java.util.Vector (§8.1.2), который может быть импортирован по требованию.


6.4.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).

  • Имена полей не могут затенять имена методов.

  • Если имя поля затенено объявлением параметра или локальной переменной, то имя параметра или локальной переменной может быть изменено без влияния на другой код.

Затенение, связанное с именами констант, встречается редко:

  • Имена констант обычно не содержат строчных букв, поэтому они обычно не будут затенять имена пакетов или типов, а также обычно не будут затенять поля, имена которых обычно содержат хотя бы одну строчную букву.

  • Имена констант не могут затенять имена методов, потому что они различаются синтаксически.

6.5. Определение смысла имени

Смысл имени зависит от контекста его использования. Определение смысла имени требует трех шагов:

  • Во-первых, контекст приводит к тому, что имя синтаксически попадает в одну из семи категорий: ModuleName, PackageName, TypeName, ExpressionName, MethodName, PackageOrTypeName или AmbiguousName.

    TypeName и MethodName менее выразительны, чем пять других категорий, потому что они обозначаются TypeIdentifier и UnqualifiedMethodIdentifier, соответственно (§3.8).

  • Во-вторых, имя, изначально классифицированное контекстом как AmbiguousName или PackageOrTypeName, затем переклассифицируется в PackageName, TypeName или ExpressionName.

  • В-третьих, результирующая категория определяет окончательное значение имени (или ошибку компиляции, если имя не имеет значения).

ModuleName:
Идентификатор
ModuleName . Идентификатор
PackageName:
Идентификатор
PackageName . Идентификатор
TypeName:
TypeIdentifier
PackageOrTypeName . TypeIdentifier
PackageOrTypeName:
Идентификатор
PackageOrTypeName . Идентификатор
ExpressionName:
Идентификатор
AmbiguousName . Идентификатор
MethodName:
UnqualifiedMethodIdentifier
AmbiguousName:
Идентификатор
AmbiguousName . Идентификатор

Использование контекста помогает минимизировать конфликты имен между сущностями разных типов. Такие конфликты будут редки, если будут соблюдаться конвенции именования, описанные в §6.1. Тем не менее, конфликты могут возникать непреднамеренно по мере развития типов, разработанных разными программистами или организациями. Например, типы, методы и поля могут иметь одинаковое имя. Всегда можно отличить метод от поля с одинаковым именем, поскольку контекст использования всегда указывает, предназначен ли метод.

6.5.1. Синтаксическая классификация имени в зависимости от контекста

Имя синтаксически классифицируется как ModuleName в этих контекстах:

  • В директиве requires в объявлении модуля (§7.7.1)

  • Справа от to в директиве exports или opens в объявлении модуля (§7.7.2)

Имя синтаксически классифицируется как PackageName в этих контекстах:

  • Справа от exports или opens в объявлении модуля

  • Слева от "." в квалифицированном PackageName

Имя синтаксически классифицируется как TypeName в этих контекстах:

  • Для именования класса или интерфейса:

    1. В директиве uses или provides в объявлении модуля (§7.7.1)

    2. В объявлении импорта одного типа (§7.5.1)

    3. Слева от . в объявлении импорта одного статического члена (§7.5.3)

    4. Слева от . в объявлении импорта всех статических членов (§7.5.4)

    5. В части permits объявления класса или интерфейса sealed (§8.1.6, §9.1.4).

    6. Слева от ( в объявлении конструктора (§8.8)

    7. После знака @ в аннотации (§9.7)

    8. Слева от .class в литерале класса (§15.8.2)

    9. Слева от .this в квалифицированном выражении this (§15.8.4)

    10. Слева от .super в выражении доступа к полю суперкласса с помощью super (§15.11.2)

    11. Слева от .Identifier или .super.Identifier в выражении вызова метода (§15.12)

    12. Слева от .super:: в выражении ссылки на метод (§15.13)

  • Как Identifier или последовательность Identifier через точки, составляющие любой ReferenceType (включая ReferenceType слева от скобок в типе массива, или слева от < в параметризованном типе, или в недиких аргументах типа параметризованного типа, или в extends или super части аргументов типа дикого типа параметризованного типа) в 17 контекстах, где используются типы (§4.11):

    1. В части extends или implements объявления класса (§8.1.4, §8.1.5)

    2. В части extends объявления интерфейса (§9.1.3)

    3. Тип возвращаемого значения метода (§8.4.5, §9.4), включая тип элемента интерфейса аннотации (§9.6.1)

    4. В части throws метода или конструктора (§8.4.6, §8.8.5, §9.4)

    5. В части extends объявления параметра типа дженерического класса, интерфейса, метода или конструктора (§8.1.2, §9.1.2, §8.4.4, §8.8.4)

    6. Тип в объявлении поля класса или интерфейса (§8.3, §9.3)

    7. Тип в объявлении формального параметра метода, конструктора или лямбда-выражения (§8.4.1, §8.8.1, §9.4, §15.27.1)

    8. Тип параметра получателя метода (§8.4)

    9. Тип в объявлении локальной переменной в операторе (§14.4.2, §14.14.1, §14.14.2, §14.20.3) или шаблоне (§14.30.1)

    10. Тип в объявлении параметра исключения (§14.20)

    11. Тип в объявлении компонента записи класса записи (§8.10.1)

    12. В явном списке аргументов типа для явного вызова конструктора, выражения создания экземпляра класса или выражения вызова метода (§8.8.7.1, §15.9, §15.12)

    13. В выражении создания экземпляра класса без указания типа, либо как тип класса, подлежащего созданию (§15.9), или как непосредственный суперкласс или непосредственный суперинтерфейс анонимного класса, подлежащего созданию (§15.9.5)

    14. Тип элемента в выражении создания массива (§15.10.1)

    15. Тип в операторе приведения типа в выражении приведения (§15.16)

    16. Тип, который следует за оператором отношения instanceof (§15.20.2)

    17. В выражении ссылки на метод (§15.13), как тип ссылки для поиска метода-члена, или как тип класса или типа массива для построения.

Извлечение TypeName из идентификаторов ReferenceType в 16 контекстах выше предназначено для рекурсивного применения ко всем подвыражениям ReferenceType, таким как его элементный тип и любые аргументы типа.

Например, предположим, что объявление поля использует тип p.q.Foo[]. Скобки типа массива игнорируются, и термин p.q.Foo извлекается как последовательность через точку Identifiers слева от скобок в типе массива и классифицируется как TypeName. На более позднем этапе определяется, является ли p, q или Foo именем типа или именем пакета.

В качестве другого примера, предположим, что оператор приведения использует тип p.q.Foo<? extends String>. Термин p.q.Foo вновь извлекается как последовательность через точку Identifier терминов, на этот раз слева от < в параметризованном типе, и классифицируется как TypeName. Термин String извлекается как Identifier в разделе extends аргумента типа с подстановочным знаком параметризованного типа и классифицируется как TypeName.

Имя синтаксически классифицируется как ExpressionName в этих контекстах:

  • В качестве квалифицирующего выражения в вызове конструктора суперкласса с квалификатором (§8.8.7.1)

  • В качестве квалифицирующего выражения в выражении создания экземпляра класса (§15.9)

  • В качестве выражения ссылки на массив в выражении доступа к массиву (§15.10.3)

  • В качестве PostfixExpression (§15.14)

  • В качестве левого операнда оператора присваивания (§15.26)

  • В качестве VariableAccess в операторе try с ресурсами (§14.20.3)

Имя синтаксически классифицируется как MethodName в этом контексте:

  • Перед "(" в выражении вызова метода (§15.12)

Имя синтаксически классифицируется как PackageOrTypeName в этих контекстах:

  • Слева от "." в квалифицированном TypeName

  • В объявлении импорта типа по требованию (§7.5.2)

Имя синтаксически классифицируется как AmbiguousName в этих контекстах:

  • Слева от "." в квалифицированном ExpressionName

  • Слева от самого правого ., который встречается перед "(" в выражении вызова метода

  • Слева от "." в квалифицированном AmbiguousName

  • В разделе по умолчанию значения объявления элемента аннотации (§9.6.2)

  • Справа от "=" в паре значений элемента аннотации (§9.7.1)

  • Слева от :: в выражении ссылки на метод (§15.13)

Эффект синтаксической классификации заключается в ограничении определенных видов сущностей определенными частями выражений:

  • Имя поля, параметра или локальной переменной может использоваться как выражение (§15.14.1).

  • Имя метода может появляться в выражении только как часть выражения вызова метода (§15.12).

  • Имя класса или интерфейса может появляться в выражении только как часть литерала класса (§15.8.2), квалифицированного выражения this (§15.8.4), выражения создания экземпляра класса (§15.9), выражения создания массива (§15.10.1), выражения приведения типа (§15.16), выражения instanceof (§15.20.2), константы перечисления (§8.9) или как часть квалифицированного имени для поля или метода.

  • Имя пакета может появляться в выражении только как часть квалифицированного имени для класса или интерфейса.

6.5.2. Переклассификация контекстно-неопределённых имён

Имя AmbiguousName затем переклассифицируется следующим образом.

Если AmbiguousName является простым именем, состоящим из одного Identifier, то:

  • Если Identifier появляется в области видимости объявления (§6.3), обозначающего локальную переменную, формальный параметр, параметр исключения или поле с таким именем (§14.4, §8.4.1, §8.8.1, §15.27.1, §14.20, §8.3), то AmbiguousName переклассифицируется как ExpressionName.

  • В противном случае, если Identifier является допустимым TypeIdentifier (§3.8) и появляется в области видимости объявления, обозначающего класс, интерфейс или параметр типа с таким именем (§8.1, §9.1, §8.4.4, §8.8.4), то AmbiguousName переклассифицируется как TypeName.

  • В противном случае, AmbiguousName переклассифицируется как PackageName. На более позднем этапе определяется, существует ли пакет с таким именем.

Если AmbiguousName является квалифицированным именем, состоящим из имени, "." и Identifier, то имя слева от "." сначала переклассифицируется, так как оно само является AmbiguousName. Затем возникает выбор:

  • Если имя слева от "." переклассифицируется как PackageName, то:

    • Если Identifier является допустимым TypeIdentifier, и существует пакет, имя которого совпадает с именем слева от ".", и этот пакет содержит объявление типа с именем, совпадающим с Identifier, то это AmbiguousName переклассифицируется как TypeName.

    • В противном случае, это AmbiguousName переклассифицируется как PackageName. На более позднем этапе определяется, существует ли пакет с таким именем.

  • Если имя слева от "." переклассифицируется как TypeName, то:

    • Если Identifier является именем метода или поля типа, обозначаемого TypeName, то это AmbiguousName переклассифицируется как ExpressionName.

    • В противном случае, если Identifier является допустимым TypeIdentifier и является именем типа-члена типа, обозначаемого TypeName, то это AmbiguousName переклассифицируется как TypeName.

    • В противном случае возникает ошибка времени компиляции.

  • Если имя слева от "." переклассифицируется как ExpressionName, то это AmbiguousName переклассифицируется как ExpressionName. На более позднем этапе определяется, существует ли член с именем Identifier.

Требование, чтобы потенциальное имя типа было "допустимым TypeIdentifier", предотвращает обработку var и yield как имени типа. Обычно это избыточно, потому что правила объявлений уже предотвращают введение типов с именами var и yield. Однако в некоторых случаях компилятор может найти двоичный класс с именем var или yield, и мы хотим чётко указать, что такие классы никогда не могут быть именованы. Простейшим решением является постоянная проверка на допустимый TypeIdentifier.

Пример 6.5.2-1. Переклассификация контекстно-неопределённых имён

Рассмотрим следующий искусственный «код библиотеки»:

package org.rpgpoet;
import java.util.Random;
public interface Music { Random[] wizards = new Random[4]; }

и затем рассмотрим этот пример кода в другом пакете:

package bazola;
class Gabriel {
    static int n = org.rpgpoet.Music.wizards.length;
}

Прежде всего, имя org.rpgpoet.Music.wizards.length классифицируется как ExpressionName, поскольку оно функционирует как PostfixExpression. Поэтому каждое из имён:

org.rpgpoet.Music.wizards
org.rpgpoet.Music
org.rpgpoet
org

сначала классифицируется как AmbiguousName. Затем они переклассифицируются:

  • Простое имя org переклассифицируется как PackageName (поскольку в области видимости нет переменной или типа с именем org).

  • Далее, предполагая, что в любом модуле компиляции пакета org нет класса или интерфейса с именем rpgpoet (и мы знаем, что такого класса или интерфейса нет, потому что пакет org содержит подпакет с именем rpgpoet), квалифицированное имя org.rpgpoet переклассифицируется как PackageName.

  • Далее, поскольку пакет org.rpgpoet имеет доступный (§6.6) тип интерфейса с именем Music, квалифицированное имя org.rpgpoet.Music переклассифицируется как TypeName.

  • Наконец, поскольку имя org.rpgpoet.Music является TypeName, квалифицированное имя org.rpgpoet.Music.wizards переклассифицируется как ExpressionName.


6.5.3. Значение имён модулей и имён пакетов

Имя модуля M, простое или квалифицированное, обозначает модуль (если таковой имеется) с этим именем.

Данный раздел не требует ошибки компиляции, если модуль с таким именем не наблюдается. Вместо этого директива requires в объявлении модуля (§7.7.1) выполняет собственную проверку имени модуля, в то время как директивы exports и opens (§7.7.2) допускают отсутствие имён модулей.

Значение имени, классифицированного как PackageName, определяется следующим образом.

6.5.3.1. Простые имена пакетов

Если имя пакета состоит из одного Identifier, то идентификатор должен появляться в области видимости ровно одного объявления верхнего уровня пакета с этим именем (§6.3), и этот пакет должен быть единственным видимым для текущего модуля (§7.4.3), в противном случае возникает ошибка времени компиляции. Значение имени пакета — это этот пакет.

6.5.3.2. Квалифицированные имена пакетов

Если имя пакета имеет вид Q.Id, то Q также должно быть именем пакета. Имя пакета Q.Id обозначает пакет, который является членом с именем Id внутри пакета, названного Q.

Если Q.Id не обозначает пакет, который единственным образом виден текущему модулю (§7.4.3), то возникает ошибка времени компиляции.

6.5.4. Значение PackageOrTypeNames

6.5.4.1. Простые PackageOrTypeNames

Если PackageOrTypeName, Q, является допустимым TypeIdentifier и встречается в области действия класса, интерфейса или параметра типа, названного Q, то PackageOrTypeName переквалифицируется как TypeName.

В противном случае PackageOrTypeName переквалифицируется как PackageName. Значение PackageOrTypeName — это значение переквалифицированного имени.

6.5.4.2. Квалифицированные PackageOrTypeNames

Для квалифицированного PackageOrTypeName вида Q.Id, если Id является допустимым TypeIdentifier и класс, интерфейс, параметр типа или пакет, обозначенный Q, имеет член-класс или интерфейс, названный Id, то имя квалифицированного PackageOrTypeName переквалифицируется как TypeName.

В противном случае оно переквалифицируется как PackageName. Значение квалифицированного PackageOrTypeName — это значение переквалифицированного имени.

6.5.5. Значение имён типов

Значение имени, классифицированного как TypeName, определяется следующим образом.

6.5.5.1. Простые имена типов

Если имя типа состоит из одного 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 является недопустимым.


6.5.5.2. Квалифицированные имена типов

Если имя типа имеет вид 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 в этом пакете.


6.5.6. Значение имён выражений

Значение имени, классифицированного как ExpressionName, определяется следующим образом.

6.5.6.1. Простые имена выражений

Если имя выражения состоит из одного Идентификатора, то в области видимости в точке, где появляется идентификатор, должно быть ровно одно объявление, обозначающее локальную переменную, формальный параметр, параметр исключения или поле. В противном случае возникает ошибка компиляции.

Если объявление обозначает переменную-инстанс класса C (§8.3.1.1), то должны быть истинны оба следующих утверждения, в противном случае возникает ошибка компиляции:

  • Имя выражения не используется в статическом контексте (§8.1.3).

  • Если имя выражения появляется во вложенном классе или интерфейсе объявления C, то непосредственно окружающее объявление класса или интерфейса имени выражения является внутренним классом C.

Например, имя выражения не должно появляться в теле метода static, объявленного классом C, ни в теле метода-инстанса класса static, вложенного во внутренний класс C.

Если объявление обозначает локальную переменную, формальный параметр или параметр исключения, пусть X — самое внутреннее объявление метода, объявление конструктора, инициализатор инстанса, статический инициализатор, объявление поля или явное вызов конструктора, которое заключает объявление локальной переменной или параметра. Если имя выражения появляется непосредственно или косвенно в теле локального класса, локального интерфейса или анонимного класса D, объявленного непосредственно в X, то должны быть истинны оба следующих утверждения, в противном случае возникает ошибка компиляции:

  • Имя выражения не используется в статическом контексте.

  • D является внутренним классом, и непосредственно окружающее объявление класса или интерфейса имени выражения является D или внутренним классом D.

Например, имя выражения не должно появляться в теле метода static, объявленного классом D, ни (если D — локальный интерфейс) в теле метода по умолчанию D.

Если объявление обозначает локальную переменную, формальный параметр или параметр исключения, которые не являются final и не являются эффективно final (§4.12.4), то возникает ошибка компиляции, если имя выражения появляется в внутреннем классе, заключённом непосредственно или косвенно в X, или в лямбда-выражении, содержащемся в X (§15.27).

Чистым результатом этих правил является то, что к локальной переменной, формальному параметру или параметру исключения можно обратиться из вложенного класса или интерфейса, объявленного в пределах его области видимости, только если (i) обращение не находится в статическом контексте, (ii) существует цепочка вложенных (не-static) классов от ссылки до объявления переменной, и (iii) переменная является final или эффективно final. Ссылки из лямбда-выражений также требуют, чтобы переменная была final или эффективно final.

Если объявление объявляет final переменную, которая определённо присваивается перед простым выражением, значение имени — значение этой переменной. В противном случае значение имени выражения — переменная, объявленная в объявлении.

Если имя выражения появляется в контексте присваивания, вызова или приведения типов, тип имени выражения — объявленный тип поля, локальной переменной или параметра после преобразования захвата (§5.1.10).

В противном случае тип имени выражения — объявленный тип поля, локальной переменной или параметра.

То есть, если имя выражения появляется "с правой стороны", его тип подвергается преобразованию захвата. Если имя выражения — переменная, которая появляется "с левой стороны", его тип не подвергается преобразованию захвата.

Пример 6.5.6.1-1. Простые имена выражений

class Test {
    static int v;
    static final int f = 3;
    public static void main(String[] args) {
        int i;
        i = 1;
        v = 2;
        f = 33;  // compile-time error
        System.out.println(i + " " + v + " " + f);
    }
}

В этой программе имена, используемые в качестве левых частей в присваиваниях к i, v и f, обозначают локальную переменную i, поле v и значение f (а не переменную f, поскольку f — final переменная). Поэтому пример порождает ошибку на этапе компиляции, потому что последнее присваивание не имеет переменной в качестве левой части. Если ошибочное присваивание удалить, изменённый код можно скомпилировать, и он выдаст вывод:

1 2 3

Пример 6.5.6.1-2. Ссылки на переменные-инстансы

class Test {
    static String a;
    String b;

    String concat1() {
        return a + b;
    }

    static String concat2() {
        return a + b;  // compile-time error
    }

    int index() {
        interface I {
            class Matcher {
                void check() {
                    if (a == null ||
                        b == null) {  // compile-time error
                        throw new IllegalArgumentException();
                    }
                }
                int match(String s, String t) {
                    return s.indexOf(t);
                }
            }
	}
        
	I.Matcher matcher = new I.Matcher();
	matcher.check();
	return matcher.match(a, b);
    }
}

Поля a и b находятся в области видимости во всем теле класса Test. Однако использование имени b в статическом контексте метода concat2 или в объявлении вложенного класса Matcher, который не является внутренним классом Test, является недопустимым.


Пример 6.5.6.1-3. Ссылки на локальные переменные и формальные параметры

class Test {
    public static void main(String[] args) {
        String first = args[0];

        class Checker {
            void checkWhitespace(int x) {
                String arg = args[x];
                if (!arg.trim().equals(arg)) {
                    throw new IllegalArgumentException();
                }
            }

            static void checkFlag(int x) {
                String arg = args[x];  // compile-time error
                if (!arg.startsWith("-")) {
                    throw new IllegalArgumentException();
                }
            }

            static void checkFirst() {
                 Runnable r = new Runnable() {
                     public void run() {
                         if (first == null) {  // compile-time error
                             throw new IllegalArgumentException();
                         }
                     }
		 };
                 r.run();
             }
        }
        
        final Checker c = new Checker();
        c.checkFirst();
        for (int i = 1; i < args.length; i++) {
            Runnable r = () -> {
                c.checkWhitespace(i);  // compile-time error
                c.checkFlag(i);  // compile-time error
            };
        }
    }
}

Формальный параметр args находится в области видимости во всем теле метода main. args является эффективно final, поэтому имя args может использоваться в методе-инстансе checkWhitespace локального класса Checker. Однако использование имени args в статическом контексте метода checkFlag локального класса Checker является недопустимым.

Локальная переменная first находится в области видимости до конца тела метода main. first также эффективно final. Однако анонимный класс, объявленный в checkFirst, не является внутренним классом Checker, поэтому использование имени first в теле анонимного класса является недопустимым. (Лямбда-выражение в теле checkFirst аналогично не сможет ссылаться на first, так как лямбда-выражение будет находиться в статическом контексте.)

Локальная переменная c находится в области видимости в последние несколько строк тела метода main и объявлена final, поэтому имя c может использоваться в теле лямбда-выражения.

Локальная переменная i находится в области видимости во всем цикле for. Однако i не является эффективно final, поэтому использование имени i в теле лямбда-выражения является недопустимым.


6.5.6.2. Квалифицированные имена выражений

Если имя выражения имеет вид Q.Id, то Q уже был классифицирован как имя пакета, имя типа или имя выражения.

Если Q является именем пакета, то возникает ошибка компиляции.

Если Q является именем типа, обозначающим тип класса, то:

  • Если не ровно один доступный член (§6.6) типа класса, который является полем, имеющим имя Id, то возникает ошибка компиляции.

  • В противном случае, если единственное доступное поле-член не является классовой переменной (то есть, оно не объявлено static), то возникает ошибка компиляции.

  • В противном случае, если классовая переменная объявлена final, то Q.Id обозначает значение классовой переменной.

    Тип выражения Q.Id — это объявленный тип классовой переменной после преобразования захвата (§5.1.10).

    Если Q.Id появляется в контексте, требующем переменную, а не значение, то возникает ошибка компиляции.

  • В противном случае, Q.Id обозначает классовую переменную.

    Тип выражения Q.Id — это объявленный тип классовой переменной после преобразования захвата (§5.1.10).

    Обратите внимание, что этот пункт охватывает использование enum констант (§8.9), так как они всегда имеют соответствующую final классовую переменную.

Если Q является именем типа, обозначающим тип интерфейса, то:

  • Если не ровно один доступный член типа интерфейса, являющийся полем с именем Id, то возникает ошибка компиляции.

  • В противном случае, Q.Id обозначает значение поля.

    Тип выражения Q.Id — это объявленный тип поля после преобразования захвата (§5.1.10).

    Если Q.Id появляется в контексте, требующем переменную, а не значение, то возникает ошибка компиляции.

Если Q — это имя выражения, пусть T — тип выражения Q:

  • Если T не является ссылочным типом, возникает ошибка компиляции.

  • Если не ровно один доступный член типа T, являющийся полем с именем Id, то возникает ошибка компиляции.

  • В противном случае, если это поле является одним из следующих:

    • Поле типа интерфейса

    • final поле типа класса (которое может быть либо классовой, либо экземплярной переменной)

    • final поле length массива типа (§10.7)

    то Q.Id обозначает значение поля, если оно не появляется в контексте, требующем переменную, а поле — это неопределённо неинициализированное пустое final поле, в этом случае оно даёт переменную.

    Тип выражения Q.Id — это объявленный тип поля после преобразования захвата (§5.1.10).

    Если Q.Id появляется в контексте, требующем переменную, а не значение, и поле, обозначенное Q.Id, определённо присвоено, то возникает ошибка компиляции.

  • В противном случае, Q.Id обозначает переменную, поле Id класса T, которое может быть либо классовой, либо экземплярной переменной.

    Тип выражения Q.Id — это тип поля-члена после преобразования захвата (§5.1.10).

Пример 6.5.6.2-1. Квалифицированные имена выражений

class Point {
    int x, y;
    static int nPoints;
}

class Test {
    public static void main(String[] args) {
        int i = 0;
        i.x++;        // compile-time error
        Point p = new Point();
        p.nPoints();  // compile-time error
    }
}

Эта программа сталкивается с двумя ошибками компиляции, потому что переменная int i не имеет членов, и потому что nPoints не является методом класса Point.


Пример 6.5.6.2-2. Квалификация выражения именем типа

Обратите внимание, что имена выражений могут квалифицироваться именами типов, но не типами вообще. Следствием является то, что нельзя получить доступ к классовой переменной через параметризованный тип. Например, приведённый код:


class Foo<T> {
    public static int classVar = 42;
}

следующее присваивание является незаконным:


Foo<String>.classVar = 91; // illegal

Вместо этого записывают:


Foo.classVar = 91;

Это не ограничивает язык программирования Java никаким существенным образом. Параметры типов не могут использоваться в типах статических переменных, и поэтому аргументы типа параметризованного типа никогда не могут повлиять на тип статической переменной. Поэтому никаких выразительных возможностей не теряется. Имя типа Foo, кажется, является сыром типом, но это не так; скорее, это имя не-обобщенного типа Foo, статический член которого должен быть получен (§6.1). Так как не используется сырой тип, нет никаких предупреждений о невыполненной проверке.


6.5.7. Значение имён методов

Значение имени, классифицированного как MethodName, определяется следующим образом.

6.5.7.1. Простые имена методов

Простое имя метода появляется в контексте выражения вызова метода (§15.12). Простое имя метода состоит из одного UnqualifiedMethodIdentifier, которое указывает имя вызываемого метода. Правила вызова метода требуют, чтобы UnqualifiedMethodIdentifier обозначал метод, находящийся в области видимости в момент вызова метода. Правила также запрещают (§15.12.3) ссылку на метод экземпляра в статическом контексте (§8.1.3), или во вложенном классе или интерфейсе, отличном от вложенного класса класса или интерфейса, который объявляет метод экземпляра.

Пример 6.5.7.1-1. Простые имена методов

Следующая программа демонстрирует роль области видимости при определении вызываемого метода.

class Super {
    void f2(String s)       {}
    void f3(String s)       {}
    void f3(int i1, int i2) {}
}

class Test {
    void f1(int i) {}
    void f2(int i) {}
    void f3(int i) {}

    void m() {
        new Super() {
            {
                f1(0);  // OK, resolves to Test.f1(int)
                f2(0);  // compile-time error
                f3(0);  // compile-time error
            }
        };
    }
}

Для вызова f1(0), в области видимости находится только один метод с именем f1. Это метод Test.f1(int), объявление которого находится в области видимости во всем теле Test, включая объявление анонимного класса. §15.12.1 выбирает поиск в классе Test, так как у объявления анонимного класса нет члена с именем f1. В конечном итоге, Test.f1(int) разрешается.

Для вызова f2(0), в области видимости находятся два метода с именем f2. Во-первых, объявление метода Super.f2(String) находится в области видимости во всем анонимном классе. Во-вторых, объявление метода Test.f2(int) находится в области видимости во всем теле Test, включая объявление анонимного класса. (Обратите внимание, что ни одно объявление не затеняет другое, потому что в момент объявления каждого из них другое не находится в области видимости). §15.12.1 выбирает поиск в классе Super, потому что он имеет член с именем f2. Однако, Super.f2(String) не применим к f2(0), поэтому возникает ошибка времени компиляции. Обратите внимание, что класс Test не проверяется.

Для вызова f3(0), в области видимости находятся три метода с именем f3. Во-первых и во-вторых, объявления методов Super.f3(String) и Super.f3(int,int) находятся в области видимости во всем анонимном классе. В-третьих, объявление метода Test.f3(int) находится в области видимости во всем теле Test, включая объявление анонимного класса. §15.12.1 выбирает поиск в классе Super, потому что он имеет член с именем f3. Однако, Super.f3(String) и Super.f3(int,int) не применимы к f3(0), поэтому возникает ошибка времени компиляции. Обратите внимание, что класс Test не проверяется.

Выбор поиска в иерархии суперклассов вложенного класса перед лексически окружающей областью видимости называется «правилом гребня» (§15.12.1).


6.6. Контроль доступа

Язык программирования Java предоставляет механизмы контроля доступа, чтобы предотвратить зависимость пользователей от пакета или класса от ненужных деталей реализации этого пакета или класса. Если доступ разрешён, то доступная сущность называется доступной.

Обратите внимание, что доступность является статическим свойством, которое можно определить во время компиляции; она зависит только от типов и модификаторов объявления.

Квалифицированные имена — это способ доступа к членам пакетов, классов, интерфейсов, параметров типа и типов ссылок. Когда имя такого члена классифицируется из своего контекста (§6.5.1) как квалифицированное имя типа (обозначающее член пакета, класса, интерфейса или параметра типа) или квалифицированное имя выражения (обозначающее член типа ссылки), применяется контроль доступа.

Например, объявление импорта одного типа использует квалифицированное имя типа (§7.5.1), поэтому именованный класс или интерфейс должен быть доступен из единицы компиляции, содержащей объявление import. В качестве другого примера, объявление класса может использовать квалифицированное имя типа для типа суперкласса (§8.1.5), поэтому опять же именованный класс должен быть доступен.

Некоторые очевидные выражения «отсутствуют» в классификации контекста в §6.5.1: доступ к полю на Primary (§15.11.1), вызов метода на Primary (§15.12), ссылка на метод через Primary (§15.13) и созданный класс в квалифицированном создании экземпляра класса (§15.9). Каждое из этих выражений использует идентификаторы, а не имена, по причине, указанной в §6.2. Следовательно, контроль доступа к членам (будь то поля, методы, классы или интерфейсы) применяется явно выражениями доступа к полю, выражениями вызова метода, выражениями ссылки на метод и выражениями квалифицированного создания экземпляра класса. (Обратите внимание, что доступ к полю также может обозначаться квалифицированным именем, встречающимся в качестве постфиксного выражения).

Кроме того, многие операторы и выражения позволяют использовать типы, которые не выражены исключительно с помощью имён типов. Например, объявление класса может использовать параметризованный тип (§4.5) для обозначения типа суперкласса. Поскольку параметризованный тип не является квалифицированным именем типа, для объявления класса необходимо явным образом выполнить контроль доступа для обозначенного суперкласса. Следовательно, из операторов и выражений, предоставляющих контексты в §6.5.1 для классификации TypeName, большинство выполняют свои собственные проверки контроля доступа.

Помимо доступа к членам пакета, класса, интерфейса или параметра типа, существует вопрос доступа к конструкторам класса. Контроль доступа необходимо проверять при явном или неявном вызове конструктора. Следовательно, проверка контроля доступа выполняется явным оператором вызова конструктора (§8.8.7.1) и выражением создания экземпляра класса (§15.9.3). Такие проверки необходимы, потому что §6.5.1 не содержит упоминания явных операторов вызова конструктора (поскольку они ссылаются на конструкторы косвенно, а не через имена) и не знает разницы между классом, обозначаемым выражением создания экземпляра класса без квалификатора, и конструктором этого класса. Кроме того, у конструкторов нет квалифицированных имён, поэтому мы не можем полагаться на проверку контроля доступа во время классификации квалифицированных имён типа.

Доступность влияет на наследование членов класса (§8.2), включая скрытие и переопределение методов (§8.4.8.1).

6.6.1. Определение доступности

  • Если класс или интерфейс верхнего уровня (§7.6) объявлен public и является членом пакета, экспортируемого модулем, то к классу или интерфейсу может получить доступ любой код в том же модуле, а также любой код в другом модуле, которому экспортирован пакет, при условии, что единица компиляции, в которой объявлен класс или интерфейс, видна этому другому модулю (§7.3).

  • Если класс или интерфейс верхнего уровня объявлен public и является членом пакета, не экспортируемого модулем, то к классу или интерфейсу может получить доступ любой код в том же модуле.

  • Если класс или интерфейс верхнего уровня объявлен с доступом по умолчанию, то к нему можно получить доступ только изнутри пакета, в котором он объявлен.

    Класс или интерфейс верхнего уровня, объявленный без модификатора доступа, неявно имеет доступ по умолчанию.

  • Член (класс, интерфейс, поле или метод) класса, интерфейса, параметра типа или типа ссылки, или конструктор класса, доступен только в том случае, если (i) класс, интерфейс, параметр типа или тип ссылки доступны, и (ii) член или конструктор объявлен так, чтобы разрешить доступ:

    • Если член или конструктор объявлен public, то доступ разрешен.

      Все члены интерфейсов, не имеющие модификаторов доступа, неявно public.

    • В противном случае, если член или конструктор объявлен protected, то доступ разрешен только в том случае, если выполняется одно из следующих условий:

      • Доступ к члену или конструктору происходит изнутри пакета, содержащего класс, в котором объявлен protected член или конструктор.

      • Доступ корректен, как описано в §6.6.2.

    • В противном случае, если член или конструктор объявлен с доступом по умолчанию, то доступ разрешен только при доступе изнутри пакета, в котором объявлен класс, интерфейс, параметр типа или тип ссылки.

      Член класса или конструктор, объявленный без модификатора доступа, неявно имеет доступ по умолчанию.

    • В противном случае член или конструктор объявлен private. Доступ разрешен только в том случае, если доступ происходит внутри тела класса или интерфейса верхнего уровня, который включает объявление члена или конструктора.

  • Тип массива доступен тогда и только тогда, когда доступен его тип элементов.

Пример 6.6-1. Управление доступом

Рассмотрим две единицы компиляции:

package points;
class PointVec { Point[] vec; }

и:

package points;
public class Point {
    protected int x, y;
    public void move(int dx, int dy) { x += dx; y += dy; }
    public int getX() { return x; }
    public int getY() { return y; }
}

которые объявляют два типа классов в пакете points:

  • Тип класса PointVec не public и не часть public интерфейса пакета points, а может использоваться только другими классами в пакете.

  • Тип класса Point объявлен public и доступен другим пакетам. Он является частью public интерфейса пакета points.

  • Методы move, getX и getY класса Point объявлены public и поэтому доступны любому коду, использующему объект типа Point.

  • Поля x и y объявлены protected и доступны вне пакета points только в подклассах класса Point, и только когда они являются полями объектов, которые реализуются кодом, который к ним обращается.

    См. §6.6.2 для примера того, как модификатор доступа protected ограничивает доступ.


Пример 6.6-2. Доступ к public полям, методам и конструкторам

Член или конструктор класса public доступен во всем пакете, где он объявлен, и из любого другого пакета, при условии, что пакет, в котором он объявлен, наблюдаем (§7.4.3). Например, в единице компиляции:

package points;
public class Point {
    int x, y;
    public void move(int dx, int dy) {
        x += dx; y += dy;
        moves++;
    }
    public static int moves = 0;
}

класс public Point имеет в качестве public членов метод move и поле moves. Эти public члены доступны любому другому пакету, имеющему доступ к пакету points. Поля x и y недоступны public и поэтому доступны только внутри пакета points.


Пример 6.6-3. Доступ к public и не-public классам

Если класс лишен public модификатора, доступ к объявлению класса ограничен пакетом, в котором он объявлен (§6.6). В примере:

package points;
public class Point {
    public int x, y;
    public void move(int dx, int dy) { x += dx; y += dy; }
}
class PointList {
    Point next, prev;
}

в единице компиляции объявлены два класса. Класс Point доступен вне пакета points, а класс PointList доступен только внутри пакета. Таким образом, единица компиляции в другом пакете может получить доступ к points.Point, либо с использованием его полного квалифицированного имени:

package pointsUser;
class Test1 {
    public static void main(String[] args) {
        points.Point p = new points.Point();
        System.out.println(p.x + " " + p.y);
    }
}

или с использованием объявления импорта одного типа (§7.5.1), в котором упоминается полное квалифицированное имя, так что после этого можно использовать простое имя:

package pointsUser;
import points.Point;
class Test2 {
    public static void main(String[] args) {
        Point p = new Point();
        System.out.println(p.x + " " + p.y);
    }
}

Однако, данная единица компиляции не может использовать или импортировать points.PointList, так как он не объявлен public и поэтому недоступен вне пакета points.


Пример 6.6-4. Доступ к полям, методам и конструкторам с доступом по умолчанию

Если ни один из модификаторов доступа public, protected или private не указан, член или конструктор класса имеют доступ по умолчанию: он доступен во всем пакете, содержащем объявление класса, в котором объявлен член класса, но член класса или конструктор недоступен ни в каком другом пакете.

Если класс public имеет метод или конструктор с доступом по умолчанию, то этот метод или конструктор недоступен для подкласса, объявленного вне этого пакета, и не наследуется.

Например, если у нас есть:

package points;
public class Point {
    public int x, y;
    void move(int dx, int dy) { x += dx; y += dy; }
    public void moveAlso(int dx, int dy) { move(dx, dy); }
}

то подкласс в другом пакете может объявить несвязанный move метод с той же сигнатурой (§8.4.2) и типом возвращаемого значения. Поскольку исходный метод move недоступен из пакета morepoints, super не может быть использован:

package morepoints;
public class PlusPoint extends points.Point {
    public void move(int dx, int dy) {
        super.move(dx, dy);  // compile-time error
        moveAlso(dx, dy);
    }
}

Поскольку move метода Point не переопределяется в move из PlusPoint, метод moveAlso в Point никогда не вызывает метод move в PlusPoint. Таким образом, если удалить вызов super.move из PlusPoint и запустить тестовую программу:

import points.Point;
import morepoints.PlusPoint;
class Test {
    public static void main(String[] args) {
        PlusPoint pp = new PlusPoint();
        pp.move(1, 1);
    }
}

она завершается нормально. Если move метода Point был переопределен в move из PlusPoint, то эта программа будет рекурсировать бесконечно, пока не произойдёт StackOverflowError.


Пример 6.6-5. Доступ к private полям, методам и конструкторам

Член или конструктор класса private доступен только внутри тела класса верхнего уровня (§7.6), который включает объявление члена или конструктора. Он не наследуется подклассами. В примере:

class Point {
    Point() { setMasterID(); }
    int x, y;
    private int ID;
    private static int masterID = 0;
    private void setMasterID() { ID = masterID++; }
}

приватные члены ID, masterID и setMasterID могут использоваться только внутри тела класса Point. К ним нельзя получить доступ с помощью квалифицированных имён, выражений доступа к полям или вызова методов вне тела объявления класса Point.

См. §8.8.10 для примера использования конструктора private.


6.6.2. Подробности о protected доступе

Член или конструктор объекта может быть доступен извне пакета, в котором он объявлен, только кодом, ответственным за реализацию этого объекта.

6.6.2.1. Доступ к protected члену

Пусть C — класс, в котором объявлен protected член. Доступ разрешен только внутри тела подкласса S класса C.

Подкласс S рассматривается как ответственный за реализацию объектов класса C. В зависимости от доступности C, S может быть объявлен в том же пакете, что и C, или в другом пакете того же модуля, что и C, или в пакете другого модуля.

Кроме того, доступ к полю экземпляра или методу экземпляра разрешен в зависимости от формы квалифицированного имени, выражения доступа к полю (§15.11), выражения вызова метода (§15.12) или выражения ссылки на метод (§15.13):

  • Если доступ осуществляется с помощью (i) квалифицированного имени вида ExpressionName.Id или TypeName.Id, или (ii) выражения доступа к полю вида Primary.Id, то доступ к полю экземпляра Id разрешен только в том случае, если квалифицирующий тип является S или подклассом S.

    Квалифицирующий тип — это тип ExpressionName или Primary, или тип, обозначаемый TypeName.

  • Если доступ осуществляется с помощью (i) выражения вызова метода вида ExpressionName.Id(...) или TypeName.Id(...) или Primary.Id(...), или (ii) выражения ссылки на метод вида ExpressionName :: Id или Primary :: Id или ReferenceType :: Id, то доступ к методу экземпляра Id разрешен только в том случае, если квалифицирующий тип является S или подклассом S.

    Квалифицирующий тип — это тип ExpressionName или Primary, или тип, обозначаемый TypeName или ReferenceType.

Дополнительную информацию о доступе к protected членам можно найти в статье «Проверка доступа к защищенным членам в виртуальной машине Java» Алессандро Коглио в журнале «Journal of Object Technology», октябрь 2005 г.

6.6.2.2. Доступ к protected конструктору

Пусть C — класс, в котором объявлен protected конструктор, и пусть S — внутренний класс, в объявлении которого используется protected конструктор. Тогда:

  • Если доступ осуществляется с помощью вызова конструктора суперкласса super(...) или квалифицированного вызова конструктора суперкласса E.super(...), где E — выражение Primary, то доступ разрешен.

  • Если доступ осуществляется с помощью выражения создания экземпляра анонимного класса new C(...){...} или квалифицированного выражения создания экземпляра анонимного класса E.new C(...){...}, где E — выражение Primary, то доступ разрешен.

  • Если доступ осуществляется с помощью выражения создания экземпляра простого класса new C(...) или квалифицированного выражения создания экземпляра класса E.new C(...), где E — выражение Primary, или выражение ссылки на метод C :: new, где C — ClassType, то доступ запрещён. К конструктору с protected доступом можно получить доступ через выражение создания экземпляра класса (не объявляющее анонимный класс) или выражение ссылки на метод только изнутри пакета, в котором он определён.

Пример 6.6.2-1. Доступ к protected полям, методам и конструкторам

Рассмотрим пример, где пакет points объявляет:

package points;
public class Point {
    protected int x, y;
    void warp(threePoint.Point3d a) {
        if (a.z > 0)  // compile-time error: cannot access a.z
            a.delta(this);
    }
}

и пакет threePoint объявляет:

package threePoint;
import points.Point;
public class Point3d extends Point {
    protected int z;
    public void delta(Point p) {
        p.x += this.x;  // compile-time error: cannot access p.x
        p.y += this.y;  // compile-time error: cannot access p.y
    }
    public void delta3d(Point3d q) {
        q.x += this.x;
        q.y += this.y;
        q.z += this.z;
    }
}

В методе delta здесь возникает ошибка компиляции: он не может получить доступ к protected членам x и y своего параметра p, поскольку, хотя Point3d (класс, в котором происходят ссылки на поля x и y) является подклассом Point (класс, в котором объявлены x и y), он не участвует в реализации Point (тип параметра p). Метод delta3d может получить доступ к protected членам своего параметра q, так как класс Point3d является подклассом Point и участвует в реализации Point3d.

Метод delta мог бы попытаться привести (§5.5, §15.16) свой параметр к типу Point3d, но это приведение потерпит неудачу, вызвав исключение, если класс p во время выполнения не являлся бы Point3d.

В методе warp также возникает ошибка компиляции: он не может получить доступ к protected члену z своего параметра a, поскольку, хотя класс Point (класс, в котором происходит ссылка на поле z) участвует в реализации Point3d (тип параметра a), он не является подклассом Point3d (класс, в котором объявлен z).


6.7. Полные имена и канонические имена

Каждый примитивный тип, именованный пакет, класс верхнего уровня и интерфейс верхнего уровня имеет полное имя:

  • Полное имя примитивного типа — это ключевое слово для этого примитивного типа, а именно 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.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API