Spec-Zone.ru › Java Language Specification 8

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

Оглавление

6.1. Объявления
6.2. Имена и идентификаторы
6.3. Область видимости объявления
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), который может использоваться в имени для ссылки на эту сущность.

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

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

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

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

  • Класс, объявленный в объявлении типа класса (§8.1)

  • Интерфейс, объявленный в объявлении типа интерфейса (§9.1)

  • Параметр типа, объявленный в рамках объявления обобщенного класса, интерфейса, метода или конструктора (§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.9)

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

      • Поле, объявленное в типе класса или типе перечисления (§8.3, §8.9.2)

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

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

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

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

      • Метод (всегда абстрактный) объявленный в типе интерфейса или типе аннотации (§9.4, §9.6.1)

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

    • Формальный параметр метода или конструктора типа класса или типа перечисления (§8.4.1, §8.8.1, §8.9.2), или лямбда-выражения (§15.27.1)

    • Формальный параметр абстрактного метода типа интерфейса или типа аннотации (§9.4, §9.6.1)

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

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

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

    • Локальная переменная, объявленная в цикле `for` (§14.14)

Конструкторы (§8.8) также вводятся объявлениями, но используют имя класса, в котором они объявлены, а не вводят новое имя.

Объявление типа, который не является обобщенным (class C ...), объявляет одну сущность: необобщенный тип (C). Необобщенный тип не является сырым типом, несмотря на синтаксическое сходство. В отличие от этого, объявление обобщенного типа (class C<T> ... или interface C<T> ...) объявляет две сущности: обобщенный тип (C<T>) и соответствующий необобщенный тип (C). В этом случае смысл термина C зависит от контекста, в котором он появляется:

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

  • Если обобщенность важна, как во всех контекстах из §6.5, за исключением контекстов без обобщения, идентификатор C обозначает либо:

    • Сырой тип C, который является стиранием (§4.6) обобщенного типа C<T>; или

    • Параметризованный тип, который является конкретной параметризацией (§4.5) обобщенного типа C<T>.

13 контекстов без обобщения следующие:

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

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

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

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

  5. После символа @ в аннотации (§9.7)

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

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

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

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

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

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

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

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

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

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

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

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

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

Имена пакетов

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

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

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

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

com.nighthacks.java.jag.scrabble
org.openjdk.tools.compiler
net.jcip.annotations
edu.cmu.cs.bovik.cheese
gov.whitehouse.socks.mousefinder

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

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

Например, пакет с именем edu.cmu.cs.bovik.cheese необязательно доступен по интернет-адресу cmu.edu или cs.cmu.edu или bovik.cs.cmu.edu.

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

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

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

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

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

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

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

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

ClassLoader
SecurityManager
Thread
Dictionary
BufferedInputStream

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

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

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

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

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

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

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


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


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

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

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

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

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

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

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

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

Имена полей

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

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

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

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

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

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

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

Например:

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

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

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

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

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

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

    • in и out, когда задействованы ввод и вывод, по образцу полей System

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

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

  • b для byte

  • c для char

  • d для double

  • e для Exception

  • f для float

  • i, j, и k для int

  • l для long

  • o для Object

  • s для String

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

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

6.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 выражения доступа к полю, выражения вызова метода, выражения ссылки на метод или выражения создания экземпляра класса; или super выражения доступа к полю, выражения вызова метода или выражения ссылки на метод; и так далее. Таким образом, мы обозначаем такие члены с помощью идентификаторов, а не простых имён.

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

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

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

Обявление считается видимым (in scope) в определённой точке программы, если и только если область видимости объявления включает эту точку.

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

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

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

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

Область видимости типа, импортированного с помощью объявления импорта одного типа (§7.5.1) или объявления импорта типов по требованию (§7.5.2), охватывает все объявления типов классов и интерфейсов (§7.6) в единице компиляции, в которой находится import объявление, а также любые аннотации объявления пакета (если таковые имеются) единицы компиляции.

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

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

Область видимости объявления члена m, объявленного или унаследованного типом класса C (§8.1.6), охватывает весь тело C, включая любые вложенные объявления типов.

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

Область видимости константы перечисления C, объявленной в типе перечисления T, — это тело T, а также все case метки switch оператора, выражение которого имеет тип перечисления T (§14.11).

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

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

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

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

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

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

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

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

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

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

  • Любые дальнейшие деклараторы справа в части ForInit оператора цикла for

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

  • Содержащийся оператор Statement

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

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

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

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

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

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

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

class PointList {
    Point first;
}

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

В следующей программе возникает ошибка компиляции, потому что инициализация локальной переменной 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 (Определённая инициализация)) перед её использованием. Она выводит:

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

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

Локальная переменная (§14.4), формальный параметр (§8.4.1, §15.27.1), параметр исключения (§14.20) и локальный класс (§14.3) могут быть обработаны только с использованием простого имени, а не квалифицированного имени (§6.2).

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

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

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

Ошибка компиляции, если имя локальной переменной v используется для объявления новой переменной в области действия v, за исключением случаев, когда новая переменная объявлена внутри класса, объявление которого находится в области действия v.

Ошибка компиляции, если имя параметра исключения используется для объявления новой переменной в блоке catch-оператор, за исключением случаев, когда новая переменная объявлена в объявлении класса, содержащемся в блоке catch-оператор.

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

Эти правила позволяют повторное объявление переменной или локального класса в объявлениях вложенных классов (локальные классы (§14.3) и анонимные классы (§15.9)), которые находятся в области действия переменной или локального класса. Таким образом, объявление формального параметра, локальной переменной или локального класса может быть затемнено в объявлении класса, вложенного в метод, конструктор или лямбда-выражение; а объявление параметра исключения может быть затемнено внутри объявления класса, вложенного в блок catch-оператор.

Существует две альтернативные стратегии обработки конфликтов имен, создаваемых параметрами лямбда-выражений и другими переменными, объявленными в лямбда-выражениях. Одна из них имитирует объявления классов: как и локальные классы, лямбда-выражения вводят новый «уровень» для имен, и все имена переменных вне выражения могут быть повторно объявлены. Другая — «локальная» стратегия: как и catch-операторы, for циклы и блоки, лямбда-выражения работают на том же «уровне», что и окружающий контекст, и локальные переменные вне выражения не могут быть затемнены. Приведенные выше правила используют локальную стратегию; нет никакого особого разрешения, позволяющего переменной, объявленной в лямбда-выражении, затенять переменную, объявленную в окружающем методе.

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

Пример 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

6.4.1. Затенение

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

Затенение отличается от скрытия (§8.3, §8.4.8.2, §8.5, §9.3, §9.5), которое применяется только к членам, которые в противном случае унаследовались бы, но не унаследованы из-за объявления в подклассе. Затенение также отличается от затемнения (§6.4.2).

Объект объявления d считается видимым в точке p в программе, если область действия d включает p, и d не затеняется никаким другим объявлением в p.

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

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

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

Объявление d локальной переменной или параметра исключения с именем n затеняет, во всей области действия d, (а) объявления любых других полей с именем n, которые находятся в области действия в точке, где происходит d, и (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. В самом деле, этот идиома обычно появляется в конструкторах (§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 указывают, что переменная будет выбрана по предпочтению перед типом, а тип — перед пакетом. Таким образом, иногда невозможно сослаться на видимый тип или объявление пакета через его простое имя. Мы говорим, что такое объявление затемнено.

Затемнение отличается от затенения (§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. Определение значения имени

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

  • Во-первых, контекст приводит имя синтаксически к одной из шести категорий: ИмяПакет, ИмяТип, ИмяВыражение, ИмяМетод, ИмяПакетИлиТип или НеоднозначноеИмя.

  • Во-вторых, имя, первоначально классифицированное контекстом как НеоднозначноеИмя или ИмяПакетИлиТип, переквалифицируется в ИмяПакет, ИмяТип или ИмяВыражение.

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

ИмяПакет:
Идентификатор
ИмяПакет . Идентификатор
ИмяТип:
Идентификатор
ИмяПакетИлиТип . Идентификатор
ИмяПакетИлиТип:
Идентификатор
ИмяПакетИлиТип . Идентификатор
ИмяВыражение:
Идентификатор
НеоднозначноеИмя . Идентификатор
ИмяМетод:
Идентификатор
НеоднозначноеИмя:
Идентификатор
НеоднозначноеИмя . Идентификатор

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

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

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

  • Первые десять негенерических контекстов (§6.1):

    1. В декларации импорта одного типа (§7.5.1)

    2. Слева от . в декларации импорта одного статического элемента (§7.5.3)

    3. Слева от . в декларации импорта всех статических элементов (§7.5.4)

    4. Слева от ( в декларации конструктора (§8.8)

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

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

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

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

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

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

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

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

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

    3. Тип возвращаемого значения метода (§8.4, §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.1)

    9. Тип в декларации локальной переменной (§14.4, §14.14.1, §14.14.2, §14.20.3)

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

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

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

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

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

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

    16. В выражении ссылки на метод (§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)

Имя синтаксически классифицируется как 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. Переклассификация контекстно-неоднозначных имён

Тогда НеоднозначноеИмя переклассифицируется следующим образом.

Если НеоднозначноеИмя является простым именем, состоящим из одного Идентификатора:

  • Если Идентификатор появляется в области действия (§6.3) объявления локальной переменной (§14.4) или параметра (§8.4.1, §8.8.1, §14.20) или объявления поля (§8.3) с этим именем, то НеоднозначноеИмя переклассифицируется как ИмяВыражения.

  • В противном случае, если поле с таким именем объявлено в единице компиляции (§7.3) содержащей Идентификатор с помощью объявления single-static-import (§7.5.3) или объявления static-import-on-demand (§7.5.4), то НеоднозначноеИмя переклассифицируется как ИмяВыражения.

  • В противном случае, если Идентификатор появляется в области действия (§6.3) класса верхнего уровня (§8 (Классы)) или объявления типа интерфейса (§9 (Интерфейсы)), объявления локального класса (§14.3) или объявления внутреннего типа (§8.5, §9.5) с этим именем, то НеоднозначноеИмя переклассифицируется как ИмяТипа.

  • В противном случае, если тип с таким именем объявлен в единице компиляции (§7.3) содержащей Идентификатор, либо с помощью объявления single-type-import (§7.5.1), либо с помощью объявления type-import-on-demand (§7.5.2), либо с помощью объявления single-static-import (§7.5.3), либо с помощью объявления static-import-on-demand (§7.5.4), то НеоднозначноеИмя переклассифицируется как ИмяТипа.

  • В противном случае, НеоднозначноеИмя переклассифицируется как ИмяПакеты. На последующем шаге определяется, существует ли пакет с таким именем.

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

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

    • Если существует пакет с именем, совпадающим с именем слева от ".", и этот пакет содержит объявление типа с именем, совпадающим с Идентификатором, то это НеоднозначноеИмя переклассифицируется как ИмяТипа.

    • В противном случае это НеоднозначноеИмя переклассифицируется как ИмяПакеты. На последующем шаге определяется, существует ли пакет с таким именем.

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

    • Если Идентификатор является именем метода или поля типа, обозначенного ИмяТипа, то это НеоднозначноеИмя переклассифицируется как ИмяВыражения.

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

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

  • Если имя слева от "." переклассифицируется как ИмяВыражения, то пусть T будет типом выражения, обозначаемого ИмяВыражения.

    • Если Идентификатор является именем метода или поля типа, обозначенного T, то это НеоднозначноеИмя переклассифицируется как ИмяВыражения.

    • В противном случае, если Идентификатор является именем внутреннего типа (§8.5, §9.5) типа, обозначенного T, то это НеоднозначноеИмя переклассифицируется как ИмяТипа.

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

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

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

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

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

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

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

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

изначально классифицируется как НеоднозначноеИмя. Затем они переклассифицируются:

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

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

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

  • Наконец, поскольку имя org.rpgpoet.Music является ИмяТипа, квалифицированное имя org.rpgpoet.Music.wizards переклассифицируется как ИмяВыражения.


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

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

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

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

Если пакет верхнего уровня с таким именем не находится в области видимости (§6.3), то возникает ошибка компиляции.

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

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

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

6.5.4. Значение PackageOrTypeNames

6.5.4.1. Простые PackageOrTypeNames

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

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

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

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

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

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

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

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

Если имя типа состоит из одного Identifier, то этот идентификатор должен встречаться в области видимости ровно одного видимого объявления типа с этим именем; в противном случае возникает ошибка компиляции. Значение имени типа — это этот тип.

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

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

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

Если Id не обозначает тип-член в Q (§8.5, §9.5), или тип-член, названный Id, в Q недоступен (§6.6), или 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());
    }
}

В этом примере имя java.util.Date должно обозначать тип, поэтому мы сначала используем рекурсивный процесс для определения, является ли java.util доступным типом или пакетом, которым оно является, а затем проверяем, является ли тип Date доступным в этом пакете.


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

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

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

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

Если объявление обозначает переменную экземпляра (§8.3), имя выражения должно появляться внутри объявления метода экземпляра (§8.4), конструктора (§8.8), инициализатора экземпляра (§8.6) или инициализатора переменной экземпляра (§8.3.2). Если имя выражения встречается в методе статического типа (§8.4.3.2), статическом инициализаторе (§8.7) или инициализаторе переменной статического типа (§8.3.2, §12.4.2), то возникает ошибка компиляции.

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

Если имя выражения появляется в контексте присваивания, вызова или приведения типа, то тип имени выражения — это объявленный тип поля, локальной переменной или параметра после преобразования захвата (§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 — переменная статического типа). Пример компилируется с ошибкой, потому что последнее присваивание не имеет переменной в качестве левой части. Если удалить ошибочное присваивание, то модифицированный код может быть скомпилирован и выведет:

1 2 3

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

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

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

Если Q — это имя типа, обозначающее тип класса (§8 (Классы)), то:

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

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

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

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

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

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

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

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

Если Q — это имя типа, обозначающее тип интерфейса (§9 (Интерфейсы)), то:

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

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

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

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

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

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

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

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

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

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

    • Поле 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. Значение имён методов

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

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

Простое имя метода появляется в контексте выражения вызова метода (§15.12). Простое имя метода состоит из одного Идентификатора, который указывает имя вызываемого метода. Правила вызова метода требуют, чтобы Идентификатор либо обозначал метод, видимый в точке вызова метода, либо обозначал метод, импортированный с помощью объявления single-static-import или static-import-on-demand (§7.5.3, §7.5.4).

Пример 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) как квалифицированное имя типа (обозначающее член пакета или ссылочного типа, §6.5.5.2) или квалифицированное имя выражения (обозначающее член ссылочного типа, §6.5.6.2), применяется управление доступом.

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

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

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

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

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

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

  • Пакет всегда доступен.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

и:

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

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

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

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

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

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

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


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

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

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

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


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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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


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

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

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

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

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


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

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

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

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

Кроме того, если Id обозначает поле или метод экземпляра, то:

  • Если доступ осуществляется по квалифицированному имени Q.Id или выражению ссылки на метод Q :: Id (§15.13), где Q является ExpressionName, то доступ разрешен тогда и только тогда, когда тип выражения Q равен S или подклассу S.

  • Если доступ осуществляется по выражению доступа к полю E.Id, или выражению вызова метода E.Id(...), или выражению ссылки на метод E :: Id, где E является выражением Primary (§15.8), то доступ разрешен тогда и только тогда, когда тип E равен S или подклассу S.

  • Если доступ осуществляется по выражению ссылки на метод T :: Id, где T является ReferenceType, то доступ разрешен тогда и только тогда, когда тип T равен S или подклассу S.

Дополнительную информацию о доступе к protected членам можно найти в статье «Проверка доступа к защищенным членам в виртуальной машине Java» Алессандро Коглио в журнале «Object Technology Journal», октябрь 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