Spec-Zone.ru › Java Language Specification 11

Глава 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) также рассматриваются в этой главе.

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)

  • Интерфейс, объявленный в объявлении типа интерфейса (§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)

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

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

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

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

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

    • Формальный параметр метода abstract типа интерфейса или типа аннотации (§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>.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  14. В декларации параметра исключения (§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 и ModuleLayer можно использовать для изоляции пакетов с одинаковыми именами друг от друга в тех случаях, когда пакеты будут иметь ограниченные взаимодействия, но не таким образом, чтобы это было прозрачно для неискушенной программы.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Область видимости члена, импортированного объявлением импорта одного статического члена (§7.5.3) или объявлением импорта статических членов по требованию (§7.5.4), включает все объявления типов классов и интерфейсов в единице компиляции, в которой появляется объявление 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-с-ресурсами (§14.20.3) — от объявления вправо через оставшуюся часть ResourceSpecification и весь блок try, связанный с оператором try-с-ресурсами.

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

Пример 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 типа с именем n затеняет объявления любых других типов с именем n, которые находятся в области действия в момент, когда происходит d, во всем объеме действия d.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • В одиннадцати первых контекстах без дженериков (§6.1):

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

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

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

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

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

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

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

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

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

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

    11. Слева от .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)

    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)

  • В качестве 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, §14.20) или объявления поля (§8.3) с этим именем, то AmbiguousName переклассифицируется как ExpressionName.

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

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

  • В противном случае, если Identifier является допустимым TypeIdentifier, и тип с таким именем объявлен в единице компиляции (§7.3), содержащей Identifier, либо с помощью объявления 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), то 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 как имени типа. Оно обычно избыточно, потому что правила объявления уже препятствуют вводу типов с именем var. Однако в некоторых случаях компилятор может найти двоичный класс с именем var, и мы хотим ясно указать, что такие классы никогда не могут иметь таких имён. Простейшим решением является последовательная проверка на допустимость 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), в противном случае происходит ошибка времени компиляции. Значение имени типа — это этот тип.

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

Если имя типа имеет вид Q.Id, то Q должно быть либо именем типа в пакете, единственным образом видимом для текущего модуля, либо именем пакета, единственным образом видимым для текущего модуля (§7.4.3).

Если 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());
    }
}

При первом запуске этой программы был выведен следующий результат:

Sun Jan 21 22:56:29 1996

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


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

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

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

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

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

Если объявление объявляет переменную 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.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. Значение имён методов

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    • В противном случае член или конструктор объявлен 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

Член или конструктор класса с модификатором доступа по умолчанию доступен во всем пакете, где он объявлен, и из любого другого пакета, при условии, что пакет, в котором он объявлен, наблюдаем (§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 имеет в качестве членов метод 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 доступе

К 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» Алессандро Коглио в журнале «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