第6章 名前

目次

6.1 デクラレーション
6.2 名前と識別子
6.3 宣言の範囲
6.3 式でのパターン変数のスコープ
6.3.1.1. 条件付き演算子&&
6.3.1.2. 条件付きまたは演算子||
6.3.1.3. 論理補完演算子!
6.3.1.4. 条件演算子? :
6.3.1.5. パターン一致演算子instanceof
6.3.1.6. switch
6.3.1.7. カッコで囲まれた式
6.3 文のパターン変数のスコープ
6.3.2.1. ブロック
6.3.2.2. if
6.3.2.3. while
6.3.2.4. do
6.3.2.5. for
6.3.2.6. switch
6.3.2.7. ラベル付き文
6.3 caseラベルのパターン変数のスコープ
6.4 シャドウイングと不明瞭化
6.4 シャドウイング
6.4 不明瞭化
6.5 名前の意味の決定
6.5 コンテキストに基づく名前の構文分類
6.5 コンテキストにあいまいな名前の再分類
6.5 モジュール名とパッケージ名の意味
6.5.3.1. 単純なパッケージ名
6.5.3.2. 修飾パッケージ名
6.5 PackageOrTypeNamesの意味
6.5.4.1. 単純なPackageOrTypeNames
6.5.4.2. 修飾されたPackageOrTypeNames
6.5 タイプ名の意味
6.5.5.1. 単純型名
6.5.5.2. 修飾タイプ名
6.5 式名の意味
6.5.6.1. 単純式名
6.5.6.2. 修飾式名
6.5 メソッド名の意味
6.5.7.1. 単純なメソッド名
6.6 アクセス制御
6.6 アクセシビリティの決定
6.6 protectedアクセスの詳細
6.6.2.1. protectedメンバーへのアクセス
6.6.2.2. protectedコンストラクタへのアクセス
6.7 完全修飾名と正規名

名前は、プログラムで宣言されているエンティティを参照するために使用されます。

宣言された実体(§6.1)は、参照型、型パラメータ、仮パラメータ、例外パラメータまたはローカル変数のパッケージ、クラス、インタフェース、メンバー(クラス、インタフェース、フィールドまたはメソッド)です。

プログラム内の名前は、単一の識別子で構成される単純、または「.」トークンで区切られた一連の識別子で構成される修飾(§6.2)のいずれかです。

名前を導入するすべての宣言には、scope (§6.3)があります。これは、宣言されたエンティティーを単純な名前で参照できるプログラムテキストの一部です。

修飾名N.xを使用して、パッケージまたは参照型のメンバーを参照できます。ここで、Nは単純名または修飾名で、xは識別子です。 Nがパッケージに名前を付ける場合、xはそのパッケージのメンバーで、クラス、インタフェースまたはサブパッケージのいずれかです。 Nが参照型または参照型の変数に名前を付ける場合、xはその型のメンバー(クラス、インタフェース、フィールドまたはメソッド)に名前を付けます。

名前の意味(§6.5)を決定する際に、出現のコンテキストを使用して、同じ名前のパッケージ、型、変数およびメソッド間であいまいさが解消されます。

アクセス制御(§6.6)は、クラス、インタフェース、メソッドまたはフィールド宣言で指定して、メンバーへのアクセスが許可されるタイミングを制御できます。 アクセス権は、有効範囲とは異なる概念です。 アクセスは、宣言済エンティティを修飾名で参照できるプログラム・テキストの一部を指定します。 宣言されたエンティティへのアクセスは、フィールド・アクセス式(§15.11)、メソッドが単純名で指定されていないメソッド呼出し式(§15.12)、メソッド参照式(§15.13)、または修飾クラス・インスタンス作成式(§15.9)にも関連しています。 アクセス修飾子がない場合、ほとんどの宣言にはパッケージ・アクセスがあり、その宣言を含むパッケージ内の任意の場所にアクセスできます。その他の可能性としては、publicprotectedおよびprivateがあります。

完全修飾および正規名(§6.7)もこの章で説明します。

6.1. デクラレーション

宣言では、次のいずれかのエンティティがプログラムに導入されます。

  • module宣言で宣言されたモジュール(§7.7)

  • package宣言で宣言されたパッケージ(§7.4)

  • 単一型インポート宣言、型インポートオンデマンド宣言または単一モジュールインポート宣言で宣言された、インポートされたクラスまたはインタフェース(§7.5.1§7.5.2§7.5.5)

  • 単一静的インポート宣言または静的インポートオンデマンド宣言で宣言された、インポートされたstaticメンバー(§7.5.3§7.5.4)

  • 通常のクラス宣言(§8.1)、列挙宣言(§8.9)、レコード宣言(§8.10)、またはコンパクト・コンパイル・ユニット(§7.3)によって暗黙的に宣言されたクラス

  • 通常のインタフェース宣言(§9.1)または注釈インタフェース宣言(§9.6)によって宣言されるインタフェース。

  • 汎用クラス、インタフェース、メソッドまたはコンストラクタの宣言の一部として宣言された型パラメータ(§8.1.2§9.1.2§8.4.4§8.8.4)

  • 参照型(§8.2§9.2§8.9.3§9.6§10.7)のメンバーで、次のいずれかです。

    • メンバー・クラス(§8.5§9.5)

    • メンバー・インタフェース(§8.5§9.5)

    • フィールド。次のいずれかになります:

      • クラスで宣言されたフィールド(§8.3)

      • インタフェースで宣言されたフィールド(§9.3)

      • enum定数またはレコード・コンポーネントに対応するクラスの暗黙的に宣言されたフィールド

      • フィールドlengthは、暗黙的にすべての配列型のメンバーです(§10.7)。

    • メソッドの1つで、次のいずれかを指定します:

      • クラスで宣言されたメソッド(abstractまたはそれ以外) (§8.4)

      • インタフェースで宣言されたメソッド(abstractまたはそれ以外) (§9.4)

      • レコード・コンポーネントに対応する暗黙的に宣言されたアクセサ・メソッド

  • 列挙定数(§8.9.1)

  • レコード・コンポーネント(§8.10.3)

  • 仮パラメータで、次のいずれか:

    • クラスまたはインタフェースのメソッドの仮パラメータ(§8.4.1)

    • クラスのコンストラクタの仮パラメータ(§8.8.1)

    • ラムダ式の仮パラメータ(§15.27.1)

  • try文のcatch句で宣言された例外ハンドラの例外パラメータ(§14.20)

  • ローカル変数は、次のいずれかです:

    • ブロック内のローカル変数宣言文によって宣言されたローカル変数(§14.4.2)

    • for文またはtry-with-resources文によって宣言されたローカル変数(§14.14§14.20.3)

    • パターンで宣言されたローカル変数(§14.30.1)

  • ローカル・クラスまたはインタフェース(§14.3)。次のいずれかです。

    • 標準クラス宣言によって宣言されたローカル・クラス

    • enum宣言によって宣言されたローカル・クラス

    • レコード宣言によって宣言されたローカル・クラス

    • 標準インタフェース宣言で宣言されたローカル・インタフェース

コンストラクタ(§8.8§8.10.4)も宣言によって導入されますが、新しい名前を導入するのではなく、宣言されているクラスの名前を使用します。

宣言には一般的に、宣言された実体を参照するために名前で使用できる識別子(§3.8)が含まれています。 この識別子には、導入されるエンティティがクラス、インタフェースまたは型パラメータである場合は特定のコンテキスト・キーワードを回避するという制約があります。

宣言に識別子が含まれず、かわりにキーワード_ (アンダースコア)が含まれている場合、エンティティを名前で参照することはできません。 アンダースコアを使用して、次の種類のエンティティを宣言できます。

  • ローカル変数は、次のいずれかです:

    • ローカル変数宣言文によって宣言されたローカル変数(§14.4.2)

    • for文またはtry-with-resources文によって宣言されたローカル変数(§14.14§14.20.3)

    • パターンで宣言されたローカル変数(§14.30.1)

  • try文のcatch句で宣言された例外ハンドラの例外パラメータ(§14.20)

  • ラムダ式の仮パラメータ(§15.27.1)

アンダースコアを使用して宣言されるローカル変数、例外パラメータまたはラムダ・パラメータは、それぞれ名前なしローカル変数名前なし例外パラメータまたは名前なしラムダ・パラメータと呼ばれます。

汎用クラスまたはインタフェース(class C<T> ...またはinterface C<T> ...)の宣言では、Cという名前のクラスと一連の型(RAW型C、パラメータ化された型C<Foo>、パラメータ化された型C<Bar>など)が導入されています。

汎用性が重要でない場合に Cへの参照が発生し、次に非汎用コンテキストの1つとして識別された場合、Cへの参照はクラスまたはインタフェース Cを示します。 他のコンテキストでは、Cへの参照は、Cによって導入された型または型の一部を示します。

15種類の非汎用コンテキストは、次のとおりです。

  1. モジュール宣言のusesまたはprovidesディレクティブ内(§7.7.1)

  2. 単一型インポート宣言(§7.5.1)

  3. 単一静的インポート宣言の.の左側(§7.5.3)

  4. 静的インポート・オンデマンド宣言の.の左側(§7.5.4)

  5. sealedクラスまたはインタフェース宣言のpermits句内(§8.1.6§9.1.4)。

  6. コンストラクタ宣言の(の左側(§8.8)

  7. @が注釈にサインインした後(§9.7)

  8. クラス・リテラル内の.classの左側(§15.8.2)

  9. 修飾this式の.thisの左側(§15.8.4)

  10. 修飾されたスーパークラス・フィールド・アクセス式の.superの左側(§15.11.2)

  11. 修飾されたメソッド呼出し式の.Identifierまたは.super.Identifierの左側(§15.12)

  12. メソッド参照式の.super::の左側(§15.13)

  13. 接頭辞式またはtry-with-resources文の修飾式名(§15.14.1§14.20.3)

  14. メソッドまたはコンストラクタのthrows句内(§8.4.6§8.8.5§9.4)

  15. 例外パラメータ宣言内(§14.20)

最初の12個の非汎用コンテキストは、§6.5.1TypeNameの最初の12個の構文コンテキストに対応します。 13番目の非汎用コンテキストでは、C.xなどの修飾ExpressionNameに、静的メンバー・アクセスを示すTypeName Cが含まれる場合があります。 これらの13のコンテキストでのTypeNameの一般的な使用は重要です。これは、これらのコンテキストに、型の最初より少ないクラスの使用が含まれていることを示しています。 一方、14番目と15番目の非汎用コンテキストでは、ClassTypeが使用され、throwsおよびcatch句では、フィールド宣言などに沿って、ファーストクラスの方法で型が使用されることを示します。 非汎用としてのこれら2つのコンテキストの特徴は、例外型をパラメータ化できないという事実によるものです(§8.1.2)。

ClassType本番では注釈が許可されるため、throws句またはcatch句での型の使用に注釈を付けることができますが、TypeName本番では注釈が許可されないため、単一型インポート宣言などの型の名前に注釈を付けることはできません。

命名規則

Java SE Platformのクラス・ライブラリは、できるかぎり下に示される規則に従って選択される名前を使用しようとします。 これらの規則によって、コードの読取りが容易になり、特定の種類の名前の競合を回避できます。

Javaプログラミング言語で作成されたすべてのプログラムでこれらの表記規則を使用することをお薦めします。 ただし、長期的な従来型の使用方法が指示を受けている場合は、これらの表記規則に従ってはなりません。 したがって、たとえば、クラスjava.lang.Mathsinメソッドとcosメソッドには、数学的に従来型の名前がありますが、これらのメソッド名は、短く動詞ではないため、ここで提案する規則を省略しています。

パッケージ名およびモジュール名

プログラマは、広く配布されているパッケージに対して「一意のパッケージ名」を選択することで、同じ名前を持つ2つの公開されたパッケージの可能性を回避するステップを実行する必要があります。 これにより、パッケージを簡単かつ自動的にインストールし、カタログ化できます。 この項では、このような一意のパッケージ名の生成に関する推奨される規則を指定します。 Java SEプラットフォームの実装は、パッケージのセットをローカルおよび不定期のパッケージ名からここに記載された一意の名前形式に変換するための自動サポートを提供することをお薦めします。

一意のパッケージ名を使用しないと、競合するパッケージのいずれかの作成時点で、パッケージ名の競合が発生する可能性があります。 この場合、ユーザーやプログラマが解決するのが困難または不可能な状況が発生することがあります。 クラスClassLoaderおよびModuleLayerを使用して、パッケージに制約付き相互作用があるが、naïveプログラムに対して透過的な方法ではない場合に、同じ名前のパッケージを相互に分離できます。

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文字で記述され、comedugovmilnetorgなどのトップ・レベルのドメイン名、またはISO Standard 3166で指定されている国を識別する英語の2文字のコードのいずれかである必要があります。

インターネット・ドメイン名が有効なパッケージ名でない場合もあります。 これらの状況に対処する場合の推奨される方法を次に示します:

  • ドメイン名にハイフンが含まれている場合、または識別子で許可されていないその他の特殊文字(§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.educs.cmu.eduまたはbovik.cs.cmu.eduから取得できるとはかぎりません。 一意のパッケージ名およびモジュール名を生成するための推奨される規則は、パッケージ名およびモジュール名の別のレジストリを作成するかわりに、既存の広く知られている一意の名前レジストリの上にパッケージおよびモジュール命名規則をまとめる方法です。

クラス名とインタフェース名

クラスの名前は、過度に長くない説明的な名詞または名詞句にし、大文字と小文字を混在させ、各単語の先頭を大文字にする必要があります。

例6.1-3.  説明的なクラス名

ClassLoader
ProcessBuilder
Thread
Dictionary
BufferedInputStream

同様に、インタフェースの名前は、過度に長くない短い説明的な名前にし、大文字と小文字を混在させ、各単語の先頭を大文字にする必要があります。 名前は、java.io.DataInputjava.io.DataOutputなどの抽象スーパークラスであるかのようにインタフェースが使用される場合に適切な、わかりやすい名詞または名詞句にすることも、インタフェースRunnableおよびCloneableのように動作を記述する形容詞にすることもできます。

型変数名

型の変数名は、(単一文字(可能な場合))がまだ明白である必要があり、小文字は使用しないでください。 これにより、型パラメータと通常のクラスおよびインタフェースを簡単に区別できます。

コンテナ・クラスおよびインタフェースでは、要素タイプにEという名前を使用する必要があります。 マップでは、キーのタイプにK、値のタイプにVを使用する必要があります。 Xという名前は、任意の例外タイプに使用する必要があります。 Tは、型を区別するために型についてより具体的なものがない場合に使用します。 (一般的なメソッドの場合に使用します。)

任意の型を示す複数の型パラメータがある場合は、Sなどのアルファベットに隣接するTの文字を使用する必要があります。 または、数値添字(T1T2など)を使用して、異なる型変数を区別することもできます。 この場合、同じプレフィクスを持つすべての変数をサブスクライブする必要があります。

汎用メソッドが汎用クラス内に表示される場合、混乱を避けるために、メソッドおよびクラスの型パラメータと同じ名前を使用しないことをお薦めします。 これは、ネストされた汎用クラスにも当てはまります。

例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;
}


型のパラメータが、前述のカテゴリのいずれにも影響しない場合、名前は単一の文字の範囲内でできるだけ意味があるものに選択する必要があります。 前述の名前(EKVXT)は、指定されたカテゴリに該当しない型パラメータには使用しないでください。

メソッド名

メソッド名は動詞または動詞のフレーズである必要があります。この場合、最初の文字は小文字に、以降の単語は大文字にした最初の文字は大文字にします。 メソッド名に関するその他の規則を次に示します:

  • 変数Vとみなされる属性を取得および設定するメソッドは、getVおよびsetVという名前にする必要があります。 たとえば、クラスThreadのメソッドgetPriorityおよびsetPriorityです。

  • 何かの長さを返すメソッドには、クラスStringのようにlengthという名前を付ける必要があります。

  • オブジェクトに関するブール条件Vをテストするメソッドには、isVという名前を付ける必要があります。 たとえば、クラスThreadのメソッドisInterruptedです。

  • オブジェクトを特定の形式Fに変換するメソッドには、toFという名前を付ける必要があります。 たとえば、クラスObjectのメソッドtoString、クラスjava.util.DateのメソッドtoLocaleStringおよびtoGMTStringです。

可能で適切な場合は常に、既存のクラス(特にJava SE Platform APIからのクラス)の名前に基づいてメソッドの名前を作成しておくと、使いやすくなります。

フィールド名

final以外のフィールドの名前は、小文字の先頭文字と後続の単語の先頭文字を大文字にして、大文字と小文字を混在させる必要があります。 適切に設計されたクラスには、定数であるフィールド(static finalフィールド)を除き、publicまたはprotectedフィールドはほとんどないことに注意してください。

フィールドには、ナウン、名詞句または名詞の略語である名前を含める必要があります。

この規則の例としては、クラスjava.io.ByteArrayInputStreamのフィールドbufposおよびcountと、クラスjava.io.InterruptedIOExceptionのフィールドbytesTransferredがあります。

定数名

インタフェース内の定数の名前は、通常、クラスのfinal変数は、1つ以上の単語、頭字語または略語のシーケンスで、すべて大文字で、コンポーネントはアンダースコア_文字で区切ります。 定数名は説明的である必要があり、意味のない短縮は必要ありません。 品詞の適切な部分である場合もあります。

定数の名前の例には、MIN_VALUEMAX_VALUEMIN_RADIXおよびクラスCharacterMAX_RADIXがあります。

集合の代替値、または整数値の使用頻度の低いマスク・ビットを表す定数のグループは、名前のプレフィクスとして一般的な頭文字で完全に指定される場合があります。

たとえば:

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

ローカル変数およびパラメータ名

ローカル変数名とパラメータ名は、短くて、わかりやすい名前にする必要があります。 通常、これらは小文字の短いシーケンスであり、次のような単語ではありません:

  • 頭字語、つまり一連の単語の最初の文字で、ColoredPointへの参照を保持する変数の場合はcpのようになります。

  • 省略形(ある種のバッファへのポインタを保持するbufなど)

  • 一般的に、従来型の名前を使用して、広く使用されているクラスにパラメータ名を付けた後に、一連のローカル変数を利用してメモリーや理解を助けるような方法で編成されます。 たとえば:

    • inおよびoutは、Systemのフィールドの後にパターン化された、なんらかの入力および出力が関係する場合

    • offおよびlenは、オフセットおよび長さが関係するたびに、java.ioのインタフェースDataInputおよびDataOutputreadおよびwriteメソッドのパラメータの後にパターン化されます。

1文字のローカル変数またはパラメータ名は、一時変数およびループ変数以外、または変数がタイプの非識別値を保持している場合を除き、使用しないでください。 従来の1文字の名前は、次のとおりです:

  • byteの場合はb

  • charの場合はc

  • doubleの場合はd

  • Exceptione

  • floatの場合はf

  • intの場合、ijおよびk

  • longの場合はl

  • Objecto

  • Stringの場合はs

  • v: 任意のタイプの任意の値

2文字または3文字のみで構成されるローカル変数またはパラメータ名は、一意のパッケージ名の最初のコンポーネントである初期国コードおよびドメイン名と競合しないようにする必要があります。

6.2.  名前および識別子

nameは、プログラムで宣言されたエンティティを参照するために使用されます。

名前には、単純名と修飾名の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);
    }
}

識別子Testmainおよびargsおよびcの最初の出現は名前ではありません。 かわりに、宣言で使用される識別子で、宣言されたエンティティの名前を指定します。 この例では、StringClassSystem.out.getClassSystem.out.printlnc.toStringargsおよびargs.lengthという名前を示します。

args.lengthは修飾名(§6.5.6.2)であり、フィールド・アクセス式(§15.11)ではないため、args.lengthでのlengthの出現は名前です。 フィールド・アクセス式、およびメソッド呼出し式、メソッド参照式および修飾クラス・インスタンス作成式では、名前ではなく識別子を使用して対象のメンバーを示します。 したがって、args[0].length()でのlengthの出現は、名前ではなく、メソッド呼出し式に出現する識別子です。

このような式では、単純名ではなく識別子が使用されるのはなぜでしょうか。 その理由は、単純な式名が字句環境の観点から定義されているためです。つまり、単純な式名は変数宣言のスコープ内に存在する必要があります(§6.5.6.1)。 一方、フィールド・アクセス、修飾されたメソッド呼出し、メソッド参照および修飾されたクラス・インスタンスの作成はすべて、名前が字句環境にないメンバーを参照します。 定義上、このような名前は、フィールド・アクセス式、メソッド呼出し式、メソッド参照式またはクラス・インスタンス作成式のプライマリによって提供されるコンテキスト内、またはフィールド・アクセス式、メソッド呼出し式またはメソッド参照式のsuperなどによってのみバインドされます。 したがって、このようなメンバーを単純名ではなく識別子で示します。

さらに複雑化するため、フィールド・アクセス式は、オブジェクトのフィールドを示す唯一の方法ではありません。 解析の理由から、スコープ内変数のフィールドを示すために修飾名が使用されます。 (変数自体は単純名で示され、上に示されます。) アクセス制御(§6.6)は、フィールドの両方の単位に適用する必要があります。

6.3.  宣言のスコープ

宣言のスコープは、宣言によって宣言されたエンティティがシャドウ化されていない場合に、単純名を使用して参照できるプログラムの領域です(§6.4.1)。

宣言は、宣言のスコープにそのポイントが含まれている場合にのみ、プログラム内の特定のポイントでスコープ内であるとみなされます。

観測可能な最上位パッケージ(§7.4.3)の宣言の範囲は、パッケージが一意に見えるモジュール(§7.4.3)に関連付けられたすべての観測可能なコンパイル単位です。

監視できないパッケージの宣言はスコープ内にありません。

サブパッケージの宣言はスコープ内にありません。

パッケージjavaは常にスコープ内にあります。

Mサイズは箱入りとなりますSサイズは箱なしでのラッピングとなりますギフトボックス・ラッピングについて_______________________________________________§7.7)およびimport宣言が出現するコンパイル・ユニットのすべてのクラスおよびインタフェース宣言(§8.1§9.1)、およびコンパイル・ユニットのモジュール宣言またはパッケージ宣言の注釈。

単一静的インポート宣言(§7.5.3)または静的インポート・オン・デマンド宣言(§7.5.4)によってインポートされるメンバーのスコープは、モジュール宣言であり、import宣言が出現するコンパイル・ユニットのすべてのクラスおよびインタフェース宣言、およびコンパイル・ユニットのモジュール宣言またはパッケージ宣言の注釈です。

通常のコンパイル単位で宣言された最上位のクラスまたはインタフェース(§7.6)のスコープは、最上位のクラスまたはインタフェースが宣言されているパッケージ内のすべてのクラスおよびインタフェース宣言です。

コンパクトコンパイル単位(§7.3)での最上位クラスの暗黙的宣言のスコープは空であり、クラスはスコープ内にありません。

クラスまたはインタフェースC(§8.2§9.2)で宣言または継承されたメンバーmの宣言のスコープは、ネストされたクラスまたはインタフェース宣言を含むCの本体全体です。 Cがレコード・クラスである場合、mのスコープには、Cのレコード宣言のヘッダーが追加で含まれます。

メソッド(§8.4.1)、コンストラクタ(§8.8.1)、またはラムダ式(§15.27)の仮パラメータの範囲は、メソッド、コンストラクタまたはラムダ式の本体全体です。

クラスの型パラメータ(§8.1.2)のスコープは、クラス宣言の型パラメータ・セクション、クラス宣言のスーパークラス型またはスーパーインタフェース型の型パラメータ・セクション、およびクラス本体です。 クラスがレコード・クラス(§8.10)である場合、型パラメータのスコープにはレコード宣言のヘッダーも含まれます(§8.10.1)。

インタフェースの型パラメータの範囲(§9.1.2)は、インタフェース宣言の型パラメータ・セクション、インタフェース宣言のスーパーインタフェース型の型パラメータ・セクション、およびインタフェース本体です。

メソッドの型パラメータ(§8.4.4)のスコープは、型パラメータ・セクションを含むメソッドの宣言全体ですが、メソッド修飾子は除外されます。

コンストラクタの型パラメータ(§8.8.4)のスコープは、型パラメータ・セクションを含むコンストラクタの宣言全体ですが、コンストラクタ修飾子は除外されます。

ブロック(§14.2)によって直ちに囲まれたローカル・クラスまたはインタフェース宣言のスコープは、ローカル・クラスまたはインタフェース宣言自体を含む、直ちに包含されるブロックの残りです。

switchブロック文グループ(§14.11)によって直ちに囲まれたローカル・クラスまたはインタフェース宣言のスコープは、ローカル・クラスまたはインタフェース宣言自体を含む、直ちに囲むswitchブロック文グループの範囲です。

ローカル変数宣言文(§14.4.2)によってブロック内で宣言されたローカル変数のスコープは、宣言独自のイニシャライザから始まり、ローカル変数宣言文の右側にさらに宣言子を含めたブロックのrestです。

基本的なfor文(§14.14.1)のForInit部分で宣言されたローカル変数のスコープには、次のすべてが含まれます。

  • 独自のイニシャライザ

  • for文のForInit部分で右側の宣言子

  • for文のおよびForUpdate部分

  • 含まれている

拡張for文のヘッダーで宣言されたローカル変数のスコープ(§14.14.2)は、含まれているです。

try-with-resources文(§14.20.3)のリソース指定で宣言されたローカル変数のスコープは、リソース指定の残りの部分およびtry-with-resources文に関連付けられたtryブロック全体に対する宣言のものです。

try-with-resources文の変換は、前述のルールを意味します。

try文のcatch句(§14.20)で宣言されている例外ハンドラのパラメータのスコープは、catchに関連付けられたブロック全体です。

例6.3-1. クラス宣言の範囲

これらの規則は、型を使用する前に、クラス型とインタフェース型の宣言が必要ないことを意味します。 次のプログラムでは、クラスPointでのPointListの使用が有効です。これは、クラス宣言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が初期化されたときに割り当てられる)ですが、ローカル変数xによってシャドウ化(§6.4.1)されるため、レッド・ハリングです。

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

次のプログラムはコンパイルします:

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

ローカル変数xは、使用前に必ず割り当てられる(§16 (Definite Assignment))ためです。 次のものが出力されます:

4

次のプログラムでは、threeのイニシャライザは、以前の宣言子で宣言された変数twoを正しく参照でき、次の行のメソッド呼出しは、ブロック内で前に宣言された変数threeを正しく参照できます。

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

このプログラムは出力を生成します:

2+1=3

パターン変数宣言のスコープ(つまり、パターンによって宣言されたローカル変数)は、パターンに対する値の照合が成功した後に実行される可能性があるプログラムの一部です(§14.30.2)。 これは、パターン変数を宣言するパターンで始まる領域で、パターン変数が完全に一致するプログラム・ポイントを考慮して決定されます。

このセクションの残りの部分は、「絶対一致」という単語の正確な説明に捧げられています。 分析では、文と式の構造が考慮され、ブール式の演算子と特定の文のフォームに関する特別な処理が行われます。

パターン変数宣言のスコープは、明確な代入(§16 (Definite Assignment))に似たフロー依存の概念であることがわかります。 このセクションの最後に定義されたルールは、意図的に明確な割当てのルールと同様の形式です。

分析は、次の形式の「導入者」の技術用語に依存します。

  • パターン変数は、trueの場合式によって導入されます

  • パターン変数はfalseの場合に式によって導入される

  • パターン変数は文によって導入されます。

最も簡単な例は、パターン変数sがtrueの場合に式a instanceof String sによって導入されることです。 つまり、式の値がtrueの場合、パターン一致は成功している必要があるため、パターン変数に値が割り当てられている必要があります。

対照的に、パターン変数tは、falseの場合、式!(b instanceof Integer t)によって導入されます。 これは、式の値がfalseの場合にのみパターン一致が成功できたためです。

6.3.1.  式のパターン変数のスコープ

特定の種類のブール式のみが、パターン変数の導入およびそれらの変数が確実に一致している場所の決定に関与します。 式が条件式、条件式または論理補完式、条件式、instanceof式、switch式またはカッコで囲まれた式でない場合、スコープ・ルールは適用されません。

6.3.1.1. 条件付き演算子&&

条件式a && b (§15.23)には、次のルールが適用されます。

  • trueの場合にaによって導入されるパターン変数は、bで確実に照合されます。

  • (i) trueの場合はaによって導入された場合、または(ii) trueの場合はbによって導入された場合、a && bによってパターン変数が導入されます。

falseの場合、a && bによってパターン変数を導入するルールがないことに注意してください。 これは、どのオペランドがfalseと評価されるかコンパイル時に判断できないためです。

次のいずれかの条件に当てはまる場合、コンパイル時エラーになります:

  • パターン変数は、(i) trueの場合はaによって導入され、(ii) trueの場合はbによって導入されます。

  • パターン変数は、(i) falseの場合はaによって導入され、(ii) falseの場合はbによって導入されます。

これらの2つのエラー・ケースでは、同じ名前のパターン変数を宣言する&&演算子のオペランドが両方とも存在する可能性は除外されます。 たとえば、問題のある式(a instanceof String s) && (b instanceof String s)について考えてみます。 最初のエラー・ケースは、trueと評価される式全体をカバーします。この場合(コードが正当な場合)、左側のオペランドと右側のオペランドの両方がtrueと評価された場合に、パターン変数sの2つの宣言を初期化する必要があります。 sという2つの変数をプログラムのrestで区別する方法がないため、式全体が間違っていると見なされます。 2番目のエラー・ケースでは、式全体がfalseと評価される反対のシナリオについて説明します。

6.3.1.2. 条件付きまたは演算子||

条件式a || b (§15.24)には、次のルールが適用されます。

  • falseの場合にaによって導入されるパターン変数は、bで確実に照合されます。

  • a || bは、(i) falseの場合にaによって導入された場合、または(ii) falseの場合にbによって導入された場合にパターン変数を導入します。

trueの場合、a || bによってパターン変数を導入するルールがないことに注意してください。 これは、どのオペランドがtrueと評価されるかコンパイル時に判断できないためです。

次のいずれかの条件に当てはまる場合、コンパイル時エラーになります:

  • パターン変数は、(i) trueの場合はaによって導入され、(ii) trueの場合はbによって導入されます。

  • パターン変数は、(i) falseの場合はaによって導入され、(ii) falseの場合はbによって導入されます。

これらの2つのエラー・ケースでは、同じ名前のパターン変数を宣言する||演算子のオペランドが両方とも存在する可能性は除外されます。 たとえば、問題のある式(a instanceof String s) || (b instanceof String s)について考えてみます。 最初のエラー・ケースは、trueと評価される式全体をカバーします。この場合(コードが正当な場合)、左側のオペランドと右側のオペランドのどちらがtrueと評価されたかに応じて、パターン変数sの宣言が正確に1つ初期化されます。 どのオペランドがtrueに評価されるか、したがってどのsの宣言を初期化するかをコンパイル時に決定できないため、式全体が間違っていると見なされます。 2番目のエラー・ケースでは、式全体がfalseと評価される反対のシナリオについて説明します。

6.3.1.3. 論理補完演算子!

論理補数式!a(§15.15.6)には、次のルールが適用されます。

  • パターン変数は、aがfalseの場合にtrue iffが導入されると、!aによって導入されます。

  • trueの場合、aによって導入されたfalse iffの場合、パターン変数は!aによって導入されます。

6.3.1.4. 条件演算子? :

条件式a ? b : c (§15.25)には、次のルールが適用されます。

  • trueの場合にaによって導入されるパターン変数は、bで確実に照合されます。

  • falseの場合にaによって導入されるパターン変数は、cで確実に照合されます。

trueまたはfalseの場合、a ? b : cによってパターン変数を導入するためのルールがないことに注意してください。 これは、オペランドatrueと評価されるかどうかをコンパイル時に判断できないためです。

次のいずれかの条件に当てはまる場合、コンパイル時エラーになります:

  • パターン変数は、(i) trueの場合はaによって導入され、(ii) trueの場合はcによって導入されます。

  • パターン変数は、(i) trueの場合はaによって導入され、(ii) falseの場合はcによって導入されます。

  • パターン変数は、(i) falseの場合はaによって導入され、(ii) trueの場合はbによって導入されます。

  • パターン変数は、(i) falseの場合はaによって導入され、(ii) falseの場合はbによって導入されます。

  • パターン変数は、(i) trueの場合はbによって導入され、(ii) trueの場合はcによって導入されます。

  • パターン変数は、(i) falseの場合はbによって導入され、(ii) falseの場合はcによって導入されます。

これらのエラー・ケースは、&&および||演算子の同様のエラー・ケースに類似しています。 これらは、? :演算子のオペランド全体で同じパターン変数の複数の宣言が発生する可能性がある、混乱するケースを排除します。

6.3.1.5. パターン一致演算子instanceof

次のルールは、パターン・オペランドa instanceof p (§15.20.2)を持つinstanceof式に適用されます。

  • パターンpにパターン変数の宣言が含まれている場合にtrueの場合、パターン変数はa instanceof pによって導入されます(§14.30.1)。

パターン変数は、別のローカル変数(§6.4)をシャドウ化することは許可されていません。

falseの場合、a instanceof pによってパターン変数を導入するルールがないことに注意してください。

6.3.1.6. switch

次のルールは、switchルール(§14.11.1)で構成されるswitchブロックを含むswitch式に適用されます。

  • switchラベルによって導入されたパターン変数は、関連するswitchルール式、switchルール・ブロックまたはswitchルールのthrow文で確実に照合されます。

switchラベル付き文グループ(§14.11.1)で構成されるswitchブロックを含むswitch式には、次のルールが適用されます。

  • switchラベルによって導入されたパターン変数は、関連するswitchラベル付き文グループのすべての文で確実に照合されます。

  • switchラベル付きステートメントグループに含まれるステートメント Sによって導入されたパターン変数は、switchラベル付きステートメントグループ内の S (存在する場合)のあとに続くすべてのステートメントで確実に照合されます。

6.3.1.7.  カッコで囲まれた式

次のルールは、カッコで囲まれた式(a) (§15.8.5)に適用されます。

  • trueの場合は(a)によってパターン変数が導入され、trueの場合はaによって導入されます。

  • falseの場合、(a)によってパターン変数が導入され、falseの場合はaによって導入されます。

6.3.2.  文のパターン変数のスコープ

パターン変数のスコープを決定する際に重要な役割を果たすのは、いくつかの種類の文のみです。

if文、while文、do文またはfor文にパターン変数を導入する式が含まれている場合、これらの変数のスコープは、特定の状況で文の部分部分を含むことができます。

たとえば、次のif-then-else文では、パターン変数sのスコープには、1つのサブステートメントが含まれますが、別のサブステートメントは含まれません。

Object o = ...
if (o instanceof String s)
    // s in scope for this substatement; no cast of o needed
    System.out.println(s.replace('*', '_'));
else
    // s not in scope for this substatement (hence, error)
    System.out.println(s);

また、特定の状況では、文内の式ではなく、文自体でパターン変数を導入できます。 文によって導入されたパターン変数は、包含ブロック内の次の文でスコープ内にあります。

たとえば、次のメソッドでは、パターン変数sのスコープに、if文の後にメソッド本体が含まれます。

void test(Object o) {
    if (!(o instanceof String s)) {
        throw new IllegalArgumentException();
    }
    // This point is only reachable if the pattern match succeeded
    // Thus, s is in scope for the rest of the block
    ...
    System.out.println(s.repeat(5));
    ...
}

6.3.2.1. ブロック

次の規則は、switchブロック(§14.11.1)ではないブロック(§14.2)に含まれるブロックステートメント Sに適用されます。

  • Sによって導入されたパターン変数は、ブロック内の S (ある場合)のあとに続くすべてのブロックステートメントで確実に照合されます。

6.3.2.2. if

if (e) S (§14.9.1)には、次のルールが適用されます。

  • trueの場合にeによって導入されるパターン変数は、Sで確実に照合されます。

  • パターン変数は、if (e) Sによって導入されます(i) falseおよび(ii) Sが正常に完了できない場合にeによって導入されます。

パターン変数を導入するif-then文に関する規則は、「正常に完了できない」(§14.22)という概念に依存し、これは定数式の概念(§15.29)に依存します。 つまり、パターン変数のスコープを計算するには、TypeName .という形式の単純名または修飾名のどちらかを判断する必要がある場合があります。 識別子は、定数変数を参照します。 パターン変数が定数変数を参照することがないため、循環性はありません。

if (e) S else T (§14.9.2)には、次のルールが適用されます。

  • trueの場合にeによって導入されるパターン変数は、Sで確実に照合されます。

  • falseの場合にeによって導入されるパターン変数は、Tで確実に照合されます。

  • 次のいずれかの場合、パターン変数はif (e) S else Tによって導入されます。

    • trueの場合、eによって導入され、Sが正常に完了でき、Tが正常に完了できない。または

    • falseの場合、eによって導入され、Sが正常に完了できず、Tが正常に完了できます。

これらのルールは、パターン変数の範囲指定の類似した性質を強調表示します。 たとえば、次のような文があるとします。

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

パターン変数sは、instanceof式によって導入され、最初の含まれる文(thenブロック内の代入文)のスコープ内にありますが、2番目の含まれる文(elseブロック内の式文)ではスコープ内にありません。

さらに、ブール式の処理と組み合せると、パターン変数のスコープは、よく知られたブール論理等価性を利用するコード・リファクタリングに対して堅牢です。 たとえば、前述のコードは次のように書き換えることができます:

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

コードは次のようにリライトすることもできますが、!演算子を二重に使用することは必ずしもお薦めしません。

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

6.3.2.3. while

while (e) S (§14.12)には、次のルールが適用されます。

  • trueの場合にeによって導入されるパターン変数は、Sで確実に照合されます。

  • パターン変数は、while (e) S iff (i) falseの場合にeによって導入され、(ii) while文がブレーク・ターゲット(§14.15)である到達可能なbreak文がSに含まれていない場合に導入されます。

6.3.2.4. do

次のルールは、文do S while (e) (§14.13)に適用されます。

  • パターン変数は、do S while (e) iff (i) falseの場合にeによって導入され、(ii) Sには、do文がブレーク・ターゲットである到達可能なbreak文が含まれていません(§14.15)。

6.3.2.5. for

基本的なfor文(§14.14.1)には、次のルールが適用されます。

  • Trueが増分部分と包含する文の両方で明確に一致している場合に、条件式によって導入されるパターン変数。

  • パターン変数は、基本的なfor文iffによって導入されます。iff (i) falseの場合に条件式によって導入され、(ii)含まれている文Sには、基本的なfor文がブレーク・ターゲットである到達可能なbreakが含まれていません(§14.15)。

拡張されたfor文(§14.14.2)は、基本的なfor文への変換によって定義されるため、特別なルールを指定する必要はありません。

6.3.2.6. switch

次のルールは、switchルール(§14.11.1)で構成されるswitchブロックを含むswitch文に適用されます。

  • switchラベルによって導入されたパターン変数は、関連するswitchルール式、switchルール・ブロックまたはswitchルールのthrow文で確実に照合されます。

switchラベル付き文グループ(§14.11.1)で構成されるswitchブロックを含むswitch文には、次のルールが適用されます。

  • switchラベルによって導入されたパターン変数は、関連するswitchラベル付き文グループのすべての文で確実に照合されます。

  • switchブロック文グループに含まれる文Sによって導入されたパターン変数は、switchブロック文グループ内のS (存在する場合)に続くすべての文で確実に照合されます。

6.3.2.7.  ラベルが付いた文

ラベル付き文(§14.7)には、次の規則が適用されます。

  • パターン変数は、ラベル付きステートメント L: S (Lはラベル) iff (i)文 Sによって導入され、(ii) Sには、ラベル付きステートメントがブレークターゲットである到達可能な breakステートメントが含まれていません(§14.15)。

6.3.3. caseラベルのパターン変数のスコープ

パターン変数は、パターン自体またはガードのいずれかでcaseパターンを持つcaseラベルによって導入でき、関連するswitch式(§6.3.1.6)またはswitch文(§6.3.2.6)の関連部分のスコープ内にあります。

次のルールがcaseラベルに適用されます。

  • パターン変数は、pにパターン変数の宣言が含まれている場合、caseパターンpを持つcaseラベルによって導入されます。

  • ガードされたcaseラベルのcaseパターンにパターン変数の宣言が含まれている場合、パターン変数は関連付けられたガード内で確実に照合されます。

  • パターン変数は、true (§6.3.1)のときに関連付けられたガードによって導入された場合、ガード付きcaseラベルによって導入されます。

6.4.  シャドウ化および不明瞭化

ローカル変数(§14.4)、仮パラメータ(§8.4.1§8.8.1§15.27.1)、例外パラメータ(§14.20)、ローカル・クラスまたはローカル・インタフェース(§14.3)は、修飾名(§6.2)ではなく単純名を使用してのみ参照できます。

ローカル変数宣言、仮パラメータ宣言、例外パラメータ宣言、ローカル・クラス宣言またはローカル・インタフェース宣言のスコープ内では、一部の宣言は許可されません。これは、単純名のみを使用して宣言されたエンティティを区別できないためです。

たとえば、メソッド本文の局所変数の名前として、メソッドの仮パラメータの名前を再宣言できる場合、局所変数によって仮パラメータがシャドウ化されますが、仮パラメータを参照する方法はありません - 望ましくない結果。

仮パラメータの名前を使用してメソッド、コンストラクタまたはラムダ式の本体内で新しい変数が宣言される場合、コンパイル時にエラーが発生します。ただし、この新しい変数が、メソッド、コンストラクタまたはラムダ式に含まれるクラスまたはインタフェース宣言で宣言される場合は除きます。

ローカル変数vの名前を使用してvのスコープ内で新しい変数を宣言する場合は、vのスコープ内に出現するクラスまたはインタフェース宣言内で新しい変数が宣言されていないかぎり、コンパイル時にエラーが発生します。

catch句のBlockに含まれるクラスまたはインタフェース宣言内で新しい変数が宣言されていないかぎり、catch句のBlock内で例外パラメータの名前を使用して新しい変数を宣言すると、コンパイル時にエラーが発生します。

ローカルクラスまたはインタフェース Cの名前を使用して Cのスコープ内で新しいローカルクラスまたはインタフェースを宣言する場合、コンパイル時にエラーが発生します。ただし、新しいローカルクラスまたはインタフェースが Cのスコープ内に出現するクラスまたはインタフェース宣言内で宣言されている場合を除きます。

これらのルールにより、変数、ローカル・クラスまたはローカル・インタフェースの範囲内で発生するネストされたクラスまたはインタフェース宣言内の変数、ローカル・クラスまたはローカル・インタフェースを再宣言できます。このようなネストされたクラスまたはインタフェース宣言は、ローカル・クラスまたはインタフェース宣言(§14.3)または匿名クラス宣言(§15.9.5)です。 したがって、仮パラメータ、ローカル変数、ローカル・クラスまたはローカル・インタフェースの宣言は、メソッド、コンストラクタまたはラムダ式内にネストされたクラスまたはインタフェース宣言でシャドウ化できます。また、例外パラメータの宣言は、catch句のブロック内にネストされたクラスまたはインタフェース宣言でシャドウ化できます。

ラムダ・パラメータで作成された名前宣言とラムダ式で宣言されたその他の変数を処理するには、2つの設計方法があります。 1つは、クラス宣言を模倣することです: ローカル・クラスのように、ラムダ式は名前の新しい"level"を導入し、式の外側にあるすべての変数名を再宣言できます。 また、catch句、forループおよびブロックと同様に、ラムダ式は包含コンテキストと同じレベルで動作し、式外のローカル変数はシャドウ化できません。 上記のルールはローカル戦略を使用します。ラムダ式で宣言された変数を、囲んでいるメソッドで宣言された変数のシャドウを作成できる特別な違反はありません。

例6.4-1.  ローカル変数のシャドウ化試行

メソッド、コンストラクタまたはイニシャライザ・ブロックのローカル変数としての識別子の宣言は、同じ名前のパラメータまたはローカル変数のスコープ内にあってはならないため、次のプログラムでコンパイル時エラーが発生します:

class Test1 {
    public static void main(String[] args) {
        int i;
        for (int i = 0; i < 10; i++)
            System.out.println(i);
    }
}

この制限は、非常に不明瞭な不具合を検出する場合に役立ちます。 スーパークラスのメンバーの追加によりサブクラスがローカル変数の名前変更を必要とする可能性があるため、ローカル変数によるメンバーのシャドウ化に対する同様の制限は現実的ではありませんでした。 関連する考慮事項は、ネストされたクラスのメンバーによるローカル変数のシャドウ化、またはネストされていないクラス内で宣言されているローカル変数によるローカル変数のシャドウ化に対する制限です。

そのため、次のプログラムは、エラーなしでコンパイルされます:

class Test2 {
    public static void main(String[] args) {
        int i;
        class Local {
            {
                for (int i = 0; i < 10; i++)
                    System.out.println(i);
            }
        }
        new Local();
    }
}

一方、同じ名前のローカル変数は、2つの別々のブロックまたはfor文で宣言でき、どちらも他方のブロックを含みません。

class Test3 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++)
            System.out.print(i + " ");
        for (int i = 10; i > 0; i--)
            System.out.print(i + " ");
        System.out.println();
    }
}

このプログラムは、エラーなしでコンパイルされ、実行時に出力を生成します:

0 1 2 3 4 5 6 7 8 9 10 9 8 7 6 5 4 3 2 1

このスタイルは、繰返しパターンで同じ名前が頻繁に使用されるパターン・マッチングでも一般的です。

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
}

class Test4 {
    static void test(Object a, Object b, Object c) {
        if (a instanceof Point p) {
            System.out.println("a is a point ("+p.x+","+p.y+")");
        }
        if (b instanceof Point p){
            System.out.println("b is a point ("+p.x+","+p.y+")");
        } else if (c instanceof Point p) {
            System.out.println("c is a point ("+p.x+","+p.y+")");
        }
    }

    public static void main(String[] args) {
        Point p = new Point(2,3);
        Point q = new Point(4,5);
        Point r = new Point(6,7);
        test(p, q, r);
    }
}

ただし、パターン変数は、ほかのパターン変数を含むローカル変数をシャドウイングすることは許可されないため、次のプログラムでは2つのコンパイル時エラーが発生します。

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
}

class Test5 {
    static void test(Object a, Object b, Object c) {
        if (a instanceof Point p) {
            System.out.println("a is a point ("+p.x+","+p.y+")");

            if (b instanceof Point p) {  // compile-time error
                System.out.println("b is a point ("+p.x+","+p.y+")");
            }
        }
    }

    public static void main(String[] args) {
        Point p = new Point(2,3);
        Point q = new Point(4,5);
        Point r = new Point(6,7);
        test(p, q, r);

        if (new Object() instanceof Point q)  // compile-time error
            System.out.println("I get your point");
    }
}


6.4.1.  シャドウ化

一部の宣言は、同じ名前の別の宣言によってスコープの一部でシャドウ化できます。この場合、宣言されたエンティティを参照するために単純な名前を使用することはできません。

シャドウイングは、非表示(§8.3§8.4.8.2§8.5§9.3§9.5)とは異なります。これは、継承されるメンバーにのみ適用されますが、サブクラスの宣言のためには適用されません。 シャドウイングも不明瞭化とは異なります(§6.4.2)。

nという名前の型の宣言 dは、dのスコープ全体で dが出現する時点でスコープ内にある nという名前のほかのすべての型の宣言をシャドウ化します。

nという名前のフィールドまたは仮パラメータの宣言 dは、dのスコープ全体で、dが出現する時点でスコープ内にある nという名前のほかのすべての変数の宣言をシャドウ化します。

nという名前のローカル変数または例外パラメータの宣言 dは、dのスコープ全体で(a) nという名前のほかのすべてのフィールドの宣言をシャドウにします。dが発生し、(b) dが出現するが、dが宣言されているもっとも内側のクラスで宣言されていない、スコープ内にある nという名前のその他の変数の宣言が発生します。

nという名前のメソッドの宣言 dは、dのスコープ全体で dが出現する時点で、包含スコープ内にある nという名前のほかのメソッドの宣言をシャドウ化します。

パッケージ宣言は他の宣言をシャドウ化しません。

nという名前の型をインポートするパッケージ pのコンパイル単位 c内のtype-import-on-demand宣言 dは、cを通して、c内の単一モジュールインポート宣言によってインポートされる nという名前の任意の型の宣言をシャドウ化します。

特に、すべてのコンパイル単位は暗黙的な宣言import java.lang.*; (§7.3)を含むかのように扱われるため、これは、パッケージjava.langからインポートされたものと同じ名前の単一モジュール・インポート宣言によってインポートされた型の宣言が常にシャドウ化されることを意味します。

nという名前の型をインポートするパッケージ pのコンパイル単位 c内の静的インポートオンデマンド宣言 dは、cを通して、c内の単一モジュールインポート宣言によってインポートされる nという名前の任意の型の宣言をシャドウ化します。

単一モジュール・インポート宣言では他の宣言がシャドウ化されることはありません。

パッケージ pのコンパイル単位 c内の単一型インポート宣言 dで、nという名前の型が cを通して次の宣言をインポートします。

  • pの別のコンパイル・ユニットで宣言されたnという名前の最上位型

  • cのオンデマンド型インポート宣言によってインポートされたnという名前の型

  • cのオンデマンド静的インポート宣言によってインポートされたnという名前の型

  • cの単一モジュール・インポート宣言によってインポートされたnという名前の型

nという名前のフィールドをインポートするパッケージpのコンパイル・ユニットcの単一静的インポート宣言dは、c全体で、cの静的インポート・オンデマンド宣言によってインポートされたnという名前の静的フィールドの宣言をシャドウ化します。

シグネチャsを持つnという名前のメソッドをインポートするパッケージpのコンパイル・ユニットc内の単一静的インポート宣言dは、cの静的インポート・オンデマンド宣言によってインポートされたシグネチャsを持つnという名前の静的メソッドの宣言を、c全体でシャドウ化します。

nという型のシャドウをインポートするパッケージpのコンパイル・ユニットcの単一静的インポート宣言d (c全体):

  • cのstatic-import-on-demand宣言によってインポートされるnという名前の静的型。

  • pの別のコンパイル・ユニット(§7.3)で宣言されたnという名前のトップ・レベル・タイプ(§7.6)

  • cのtype-import-on-demand宣言(§7.5.2)によってインポートされるnという名前の任意の型。

  • cの単一モジュール・インポート宣言(§7.5.5)によってインポートされたnという名前の任意の型。

例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

  • クラスTestのメンバーであるクラス(static)変数x

  • クラスTestのメンバーであるクラス・メソッドmain

  • mainメソッドのパラメータargs

  • mainメソッドのローカル変数x

クラス変数のスコープにはクラスの本体全体が含まれるため(§8.2)、クラス変数xは通常、メソッドmainの本体全体で使用可能になります。 ただし、この例では、クラス変数xは、ローカル変数xの宣言によって、メソッドmainの本体内でシャドウイングされます。

ローカル変数は、そのスコープとして、宣言されているブロックの残り(§6.3)を持ちます。この場合、これはmainメソッドの本体、つまりイニシャライザ「0」とSystem.out.printおよびSystem.out.printlnの呼出しの残りです。

これは、次のことを意味します。

  • printの呼出しの式xは、ローカル変数xの値を参照(示)します。

  • printlnの呼出しでは、修飾名(§6.6) Test.xが使用されます。これは、クラス型名Testを使用してクラス変数xにアクセスするためです。これは、Test.xの宣言は現時点でシャドウ化され、その単純名で参照できないためです。

キーワードthisは、this.xの形式を使用して、シャドウ・フィールド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

オンデマンドでインポートされる可能性のある汎用クラスjava.util.Vector (§8.1.2)よりも、ここで宣言されたクラスVectorを使用します。


6.4.2.  不明瞭化

単純な名前は、変数、型またはパッケージの名前として解釈される可能性があるコンテキストで発生する可能性があります。 このような場合、§6.5.2の規則では、型よりも優先して変数が選択され、パッケージよりも型が選択されることを規定しています。 したがって、宣言がスコープ内にあり、シャドウ化されていない場合でも、単純名で型またはパッケージを参照できないことがあります。 このような宣言は不明瞭です。

不明瞭化は、シャドウイング(§6.4.1)および非表示(§8.3§8.4.8.2§8.5§9.3§9.5)とは異なります。

モジュールの名前と変数、型またはパッケージの名前の間にはわかりにくいものはありません。したがって、モジュールは変数、型およびパッケージと名前を共有できますが、含まれるパッケージの後にモジュールに名前を付けることは必ずしもお薦めしません。

§6.1の命名規則は曖昧さの軽減に役立ちますが、もしそうなったら、それを避けるために何ができるかについての注意点がいくつかあります。

式でパッケージ名が発生した場合:

  • パッケージ名がフィールド宣言によって不明瞭化されている場合、通常、import宣言(§7.5)を使用して、そのパッケージで宣言された型名を使用できます。

  • パッケージ名がパラメータまたはローカル変数の宣言によって不明瞭化されている場合、パラメータまたはローカル変数の名前は、他のコードに影響を与えずに変更できます。

通常、型名は大文字で始まり、型名は大文字で始まっているため、パッケージ名の最初のコンポーネントは型名と間違えることはほとんどありません。 (Javaプログラミング言語は、名前がパッケージ名かタイプ名かを判断するために大/小文字の区別に依存しません。)

クラスおよびインタフェース・タイプ名を含む不明瞭化はまれです。 通常、フィールド、パラメータおよびローカル変数の名前は型名を不明瞭にしません。型名は小文字で始まり、型名は大文字で始まります。

メソッド名を不明瞭にしたり、他の名前で不明瞭にすることはできません(§6.5.7)。

フィールド名に関連する廃止はまれですが、次のような場合があります:

  • フィールド名がパッケージ名を隠す場合、通常、import宣言(§7.5)を使用して、そのパッケージで宣言された型名を使用できるようにします。

  • フィールド名が型名を隠す場合、型名がローカル・クラスまたはインタフェース(§14.3)を示さないかぎり、その型の完全修飾名を使用できます。

  • フィールド名でメソッド名を隠すことはできません。

  • フィールド名がパラメータまたはローカル変数の宣言によってシャドウイングされている場合、他のコードに影響を与えることなく、パラメータまたはローカル変数の名前を変更できます。

定数名を含む不明瞭化はまれです。

  • 通常、定数名には小文字が含まれないため、通常はパッケージや型の名前を不明瞭にしたり、通常は名前が小文字を少なくとも1つ含むフィールドをシャドウ化したりしません。

  • 定数名は構文的に区別されるため、メソッド名を不明瞭化できません。

6.5.  名前の意味の確認

名前の意味は、使用されるコンテキストに依存します。 名前の意味の決定には、次の3つのステップが必要です。

  • まず、コンテキストにより、名前の構文が7つのカテゴリ(ModuleNamePackageNameTypeNameExpressionNameMethodNamePackageOrTypeNameまたはAmbiguousName)のいずれかに分類されます。

    TypeNameおよびMethodNameは、TypeIdentifierおよびUnqualifiedMethodIdentifier (§3.8)で示されるため、他の5つのカテゴリより表現性が低くなります。

  • 次に、コンテキストによって最初にAmbiguousNameとして、またはPackageOrTypeNameとして分類される名前は、PackageNameTypeNameまたはExpressionNameとして再分類されます。

  • 3番目に、結果として得られたカテゴリにより、名前の意味の最終決定が行われます(または、名前に意味がない場合はコンパイル時にエラーが発生します)。

コンテキストの使用は、異なる種類のエンティティ間での名前の競合を最小化するために役立ちます。 このような競合は、§6.1に記載されている命名規則に従えばまれです。 それでも、別のプログラマや別の組織が開発した型が展開されると、意図せずに競合が発生する場合があります。 たとえば、型、メソッドおよびフィールドが同じ名前になる場合があります。 使用されるコンテキストから、メソッドを意図しているかどうかが常にわかるため、同じ名前のメソッドとフィールドを常に区別できます。

6.5.1.  コンテキストに応じた名前の構文的分類

名前は、次のコンテキストで構文的にModuleNameとして分類されます:

  • モジュール宣言のrequiresディレクティブ内(§7.7.1)

  • モジュール宣言のexportsまたはopensディレクティブのtoの右側(§7.7.2)

  • 単一モジュール・インポート宣言のmoduleの右側(§7.5.5)

名前は、次のコンテキストで構文的にPackageNameとして分類されます:

  • モジュール宣言のexportsまたはopensの右側

  • 修飾PackageNameの"."の左側

名前は、次のコンテキストで構文的にTypeNameとして分類されます:

  • クラスまたはインタフェースを指定する場合:

    1. モジュール宣言のusesまたはprovidesディレクティブ内(§7.7.1)

    2. 単一型インポート宣言(§7.5.1)

    3. 単一静的インポート宣言の.の左側(§7.5.3)

    4. 静的インポート・オンデマンド宣言の.の左側(§7.5.4)

    5. sealedクラスまたはインタフェース宣言のpermits句内(§8.1.6§9.1.4)。

    6. コンストラクタ宣言の(の左側(§8.8)

    7. @が注釈にサインインした後(§9.7)

    8. クラス・リテラル内の.classの左側(§15.8.2)

    9. 修飾this式の.thisの左側(§15.8.4)

    10. 修飾されたスーパークラス・フィールド・アクセス式の.superの左側(§15.11.2)

    11. 修飾されたメソッド呼出し式の.Identifierまたは.super.Identifierの左側(§15.12)

    12. メソッド参照式の.super::の左側(§15.13)

  • 任意のReferenceTypeを構成するIdentifierシーケンス(配列型のカッコの左側にあるReferenceTypeを含む)、または<の左側にあるReferenceTypeを含む)のIdentifierまたはドット付きシーケンスとして型が使用される17コンテキスト(§4.11)のパラメータ化された型、またはパラメータ化された型のワイルドカード型以外の引数、またはパラメータ化された型のワイルドカード型引数のextends句またはsuper句):

    1. クラス宣言のextendsまたはimplements句内(§8.1.4§8.1.5)

    2. インタフェース宣言のextends句内(§9.1.3)

    3. メソッドの戻り型(§8.4.5§9.4)。注釈インタフェースの要素の型を含みます(§9.6.1)。

    4. メソッドまたはコンストラクタのthrows句(§8.4.6§8.8.5§9.4)

    5. 汎用クラス、インタフェース、メソッドまたはコンストラクタの型パラメータ宣言のextends句(§8.1.2§9.1.2§8.4.4§8.8.4)

    6. クラスまたはインタフェースのフィールド宣言内の型(§8.3§9.3)

    7. メソッド、コンストラクタまたはラムダ式の仮パラメータ宣言の型(§8.4.1§8.8.1§9.4§15.27.1)

    8. メソッドの受信側パラメータのタイプ(§8.4)

    9. 文(§14.4.2§14.14.1§14.14.2§14.20.3)またはパターン(§14.30.1)のいずれかのローカル変数宣言内の型

    10. 例外パラメータ宣言の型(§14.20)

    11. レコード・クラスのレコード・コンポーネント宣言の型(§8.10.1)

    12. コンストラクタ呼出し、クラス・インスタンス作成式またはメソッド呼出し式への明示的な型引数リスト(§8.8.7.1§15.9§15.12)

    13. インスタンス化するクラス・タイプ(§15.9)またはインスタンス化する匿名クラスの直接スーパークラスまたは直接スーパーインタフェース(§15.9.5)としての不適格クラス・インスタンス作成式

    14. 配列作成式の要素型(§15.10.1)

    15. キャスト式のキャスト演算子の型(§15.16)

    16. instanceof関係演算子に続く型(§15.20.2)

    17. メソッド参照式(§15.13)では、メンバー・メソッドを検索するための参照型として、または構築するクラス型または配列型として。

前述の17コンテキストのReferenceTypeの識別子からのTypeNameの抽出は、要素タイプや任意の型引数など、ReferenceTypeのすべてのサブ用語に再帰的に適用することを目的としています。

たとえば、フィールド宣言でp.q.Foo[]型が使用されているとします。 配列型の大カッコは無視され、p.q.Fooという用語は、配列型の大カッコの左側にある識別子のドット付きシーケンスとして抽出され、TypeNameとして分類されます。 後のステップでは、pqおよびFooのうち、型名またはパッケージ名であるものが決定されます。

別の例として、キャスト演算子でp.q.Foo<? extends String>型が使用されているとします。 用語p.q.Fooは、識別子用語のドット付きシーケンスとして再度抽出され、今回はパラメータ化された型の<の左側に抽出され、TypeNameとして分類されます。 Stringという用語は、パラメータ化された型のワイルドカード型引数のextends句で識別子として抽出され、TypeNameとして分類されます。

名前は、次のコンテキストで構文的にExpressionNameとして分類されます:

  • 修飾されたスーパークラス・コンストラクタ呼出しの修飾式として(§8.8.7.1)

  • 修飾クラス・インスタンス作成式の修飾式として(§15.9)

  • 配列アクセス式の配列参照式として(§15.10.3)

  • PostfixExpressionとして(§15.14)

  • 代入演算子の左オペランドとして(§15.26)

  • try-with-resources文のVariableAccessとして(§14.20.3)

このコンテキストでは、名前は構文的にMethodNameとして分類されます:

  • メソッド呼出し式の"("の前(§15.12)

名前は、次のコンテキストで構文的にPackageOrTypeNameとして分類されます:

  • 修飾TypeNameの「.」の左側

  • type-import-on-demand宣言(§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が単一の識別子で構成される単純な名前である場合、次のようになります:

  • 識別子が、ローカル変数、仮パラメータ、例外パラメータ、またはその名前を持つフィールド(§14.4§8.4.1§8.8.1§15.27.1§14.20§8.3)を示す宣言(AmbiguousName)の範囲内に存在する場合、ExpressionNameとして再分類されます。

  • それ以外の場合、Identifierが有効なTypeIdentifier (§3.8)であり、その名前(§8.1§9.1§8.4.4§8.8.4)を持つクラス、インタフェースまたは型パラメータを示す宣言のスコープ内に表示される場合(AmbiguousName)、TypeNameとして再分類されます。

  • それ以外の場合、AmbiguousNamePackageNameとして再分類されます。 後のステップで、その名前のパッケージが実際に存在するかどうかを確認します。

AmbiguousNameが名前、.およびIdentifierで構成される修飾名である場合、「.」の左側の名前は最初に再分類されます。それ自体はAmbiguousNameです。 次の選択肢があります:

  • "."の左側の名前がPackageNameとして再分類される場合:

    • Identifierが有効なTypeIdentifierで、名前が.の左側にあるパッケージがあり、そのパッケージにIdentifierと同じ名前の型の宣言が含まれている場合、このAmbiguousNameTypeNameとして再分類されます。

    • それ以外の場合、このAmbiguousNamePackageNameとして再分類されます。 後のステップで、その名前のパッケージが実際に存在するかどうかを確認します。

  • .」の左側の名前がTypeNameとして再分類される場合:

    • IdentifierTypeNameで示されるタイプのメソッドまたはフィールドの名前である場合、このAmbiguousNameExpressionNameとして再分類されます。

    • それ以外の場合、識別子が有効なTypeIdentifierで、TypeNameで示されるタイプのメンバー・タイプの名前である場合、このAmbiguousNameTypeNameとして再分類されます。

    • そうでない場合、コンパイル時にエラーが発生します。

  • .の左側の名前がExpressionNameとして再分類される場合、このAmbiguousNameExpressionNameとして再分類されます。 後のステップでは、「識別子」という名前のメンバーが実際に存在するかどうかを決定します。

潜在的なタイプ名が「有効なTypeIdentifier」であるという要件により、varおよびyieldをタイプ名として処理できません。 宣言のルールによってすでにvarおよびyieldという名前の型の導入が妨げられているため、通常は冗長です。 ただし、コンパイラでvarまたはyieldという名前のバイナリ・クラスが検出される場合があり、このようなクラスに名前を付けることはできないことを明確にしたい場合があります。 最も簡単な解決策は、有効なTypeIdentifierを一貫してチェックすることです。

例6.5.2-1.  コンテキスト的にあいまいな名前の再分類

次の非導出"ライブラリ・コード"について考えてみます:

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

次に、別のパッケージの次のサンプル・コードを考えてみます:

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

まず、org.rpgpoet.Music.wizards.lengthという名前は、PostfixExpressionとして機能するため、ExpressionNameとして分類されます。 したがって、各名前は次のようになります:

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

最初はAmbiguousNameとして分類されます。 その後、これらは再分類されます:

  • 単純名orgは、PackageNameとして再分類されます(スコープにorgという名前の変数または型がないため)。

  • 次に、パッケージorgのコンパイル・ユニットにrpgpoetという名前のクラスまたはインタフェースがないと仮定します(また、パッケージorgにはrpgpoetという名前のサブパッケージがあるため、そのようなクラスまたはインタフェースがないことがわかっています)。修飾名org.rpgpoetは、PackageNameとして再分類されます。

  • 次に、パッケージorg.rpgpoetにはMusicという名前のアクセス可能な(§6.6)インタフェース・タイプがあるため、修飾名org.rpgpoet.MusicTypeNameとして再分類されます。

  • 最後に、org.rpgpoet.Musicという名前がTypeNameであるため、修飾名org.rpgpoet.Music.wizardsExpressionNameとして再分類されます。


6.5.3. モジュール名とパッケージ名の意味

モジュール名Mは、単純か修飾かに関係なく、その名前のモジュール(存在する場合)を示します。

このセクションは、その名前を持つモジュールが監視可能でない場合、コンパイル時エラーを要求しません。 かわりに、モジュール宣言のrequiresディレクティブ(§7.7.1)はモジュール名の独自の検証を実行しますが、exportsおよびopensディレクティブ(§7.7.2)は存在しないモジュール名の許容範囲です。

PackageNameとして分類される名前の意味は、次のように決定されます。

6.5.3.1. 単純なパッケージ名

パッケージ名が単一の識別子で構成されている場合、識別子は、この名前を持つ最上位パッケージの1つの宣言(§6.3)のスコープ内に存在しなければならず、そのパッケージは現在のモジュール(§7.4.3)に一意に見える必要があります。そうしないと、コンパイル時にエラーが発生します。 パッケージ名の意味は、そのパッケージです。

6.5.3.2. 修飾パッケージ名

パッケージ名の形式がQ.Idの場合、Qもパッケージ名である必要があります。 パッケージ名Q.Idは、Qで指定されたパッケージ内のIdという名前のメンバーであるパッケージに名前を付けます。

Q.Idが、現在のモジュール(§7.4.3)から一意に見えるパッケージに名前を付けない場合、コンパイル時にエラーが発生します。

6.5.4. PackageOrTypeNamesの意味

6.5.4.1. 単純なPackageOrTypeNames

PackageOrTypeName (Q)が有効なTypeIdentifierであり、Qという名前のクラス、インタフェースまたは型パラメータのスコープ内にある場合、PackageOrTypeNameTypeNameとして再分類されます。

それ以外の場合、PackageOrTypeNamePackageNameとして再分類されます。 PackageOrTypeNameの意味は、再分類された名前の意味です。

6.5.4.2. 修飾されたPackageOrTypeNames

Q.Id形式の修飾PackageOrTypeNameが指定された場合、Idが有効なTypeIdentifierであり、Qで示されるクラス、インタフェース、タイプ・パラメータまたはパッケージにIdという名前のメンバー・クラスまたはインタフェースがある場合、修飾されたPackageOrTypeName名はTypeNameとして再分類されます。

それ以外の場合は、PackageNameとして再分類されます。 修飾されたPackageOrTypeNameの意味は、再分類された名前の意味です。

6.5.5.  型名の意味

TypeNameとして分類される名前の意味は、次のように決定されます。

6.5.5.1.  単純な型名

型名が単一の識別子で構成されている場合、識別子は、この名前を持つクラス、インタフェースまたは型パラメータの1つの宣言(§6.3)のスコープ内に存在する必要があります。そうしないと、コンパイル時にエラーが発生します。

宣言が汎用クラスまたはインタフェース C (§8.1.2§9.1.2)の型パラメータを示している場合、次の両方が真である必要があり、そうでなければコンパイル時にエラーが発生します。

  • 型名は静的コンテキストでは発生しません(§8.1.3)。

  • Cのネストされたクラスまたはインタフェース宣言に型名が含まれている場合、その型名の直近の包含クラスまたはインタフェース宣言はCの内部クラスです。

たとえば、型名は、Cで宣言されたstaticメソッドの本体にも、C内でネストされたstaticクラスのインスタンス・メソッドの本体にも指定できません。

宣言が汎用メソッドまたはコンストラクタmの型パラメータを示しており(§8.4.4§8.8.4)、型名がmの本体で直接宣言されたローカル・クラス、ローカル・インタフェースまたは匿名クラスDの本体に直接的または間接的に出現する場合、次の両方がtrueである必要があり、そうでないとコンパイル時にエラーが発生します。

  • タイプ名は静的コンテキストでは発生しません。

  • Dは内部クラスであり、タイプ名の直前に包含されるクラスまたはインタフェース宣言は Dまたは内部クラス Dです。

たとえば、型名は、Dで宣言されたstaticメソッドの本体にも、デフォルト・メソッドDの本体にも(Dがローカル・インタフェースの場合)指定できません。

型名の意味は、スコープ内クラス、インタフェース、または型パラメータです。

例 6.5.5.1-1.  型パラメータへの参照

class Box<T> {
    T val;
    Box(T t) { val = t; }

    static Box<T> empty() {  // compile-time error
        return new Box<>(null);
    }

    static <U> Box<U> make(U val) {
        interface Checker {
            void check(U val);  // compile-time error
        }

        class NullChecker implements Checker {
            public void check(U val) {
                if (val == null) {
                    throw new IllegalArgumentException();
                }
            }
        }

        new NullChecker().check(val);
        return new Box<U>(val);
    }
}

クラス型パラメータTは、クラスBoxの宣言全体を通してスコープ内にありますが、staticメソッドemptyの宣言にTという名前を使用することは不正です。

同様に、メソッド型パラメータUは、メソッドmakeの宣言全体を通してスコープ内にありますが、(暗黙的にstatic)ローカル・インタフェースCheckerの宣言でUという名前を使用することは不正です。


6.5.5.2.  修飾された型名

型名がQ.Idの形式の場合、Qは、現在のモジュールに一意に見えるパッケージ内のクラス、インタフェースまたは型パラメータの名前、または現在のモジュールに一意に見えるパッケージの名前(§7.4.3)のいずれかである必要があります。

Idが、Qで示されるクラス、インタフェース、型パラメータまたはパッケージのメンバーであるアクセス可能なクラスまたはインタフェース(§6.6)を1つのみ指定する場合、修飾型名はそのクラスまたはインタフェースを示します。

IdQ (§8.5§9.5)内のメンバー・クラスまたはインタフェースに名前を付けていない場合、またはQ内のIdという名前のメンバー・クラスまたはインタフェースにアクセスできない場合、またはIdQ内の複数のメンバー・クラスまたはインタフェースに名前を付けている場合は、コンパイル時にエラーが発生します。

例 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.  単純な式名

式名が単一の識別子で構成されている場合、次のようになります:

  • 式名がswitchラベル(§14.11.1)にCaseConstantとして表示され、包含するswitch文またはswitch式のセレクタ式のタイプがenumクラス・タイプ(§8.9)であり、enumクラスがIdentifierという名前のenum定数を宣言する場合、式名はenumクラスの対応する暗黙フィールドを参照します。

  • それ以外の場合、識別子が発生した時点でローカル変数、仮パラメータ、例外パラメータまたはスコープ内のフィールドを示す宣言が1つのみ存在する場合、式名はスコープ内変数を参照します。

  • そうでない場合、コンパイル時にエラーが発生します。

宣言がクラス C (§8.3.1.1)のインスタンス変数を示す場合、次のすべてに当てはまるか、コンパイル時にエラーが発生します。

  • 式名は静的コンテキストでは発生しません(§8.1.3)。

  • 式名がCの初期構成コンテキスト(§8.8.7)で発生する場合、単純な代入式の左側のオペランド(§15.26)であり、名前付き変数の宣言にはイニシャライザがなく、単純な代入式は、Cの初期構成コンテキストに含まれるラムダ式または内部クラス宣言で囲まれていません。

  • 式名は、Cのサブクラスの早期構築コンテキストに出現しない。

  • Cのネストされたクラスまたはインタフェース宣言に式名が含まれている場合、式名の直近の包含クラスまたはインタフェース宣言は、Cの内部クラスです。

たとえば、式名は、Cで宣言されたstaticメソッドの本体にも、C内でネストされたstaticクラスのインスタンス・メソッドの本体にも指定できません。

宣言がローカル変数、仮パラメータまたは例外パラメータを示している場合、Xは、ローカル変数またはパラメータ宣言を囲む最も内側のメソッド宣言、コンストラクタ宣言、インスタンス・イニシャライザ、静的イニシャライザ、フィールド宣言またはコンストラクタ呼出しになります。 Xで直接宣言されたローカル・クラス、ローカル・インタフェースまたは無名クラスDの本体に式名が直接的または間接的に現れる場合は、次の両方に当てはまる必要があり、そうでないとコンパイル時にエラーが発生します。

  • 式名が静的コンテキストにない。

  • Dは内部クラスで、式名の直近のクラスまたはインタフェース宣言はDまたは内部クラスDです。

たとえば、式名は、Dで宣言されたstaticメソッドの本体にも、デフォルト・メソッドDの本体にも(Dがローカル・インタフェースの場合)指定できません。

宣言が、finalでも事実上finalでもないローカル変数、仮パラメータまたは例外パラメータ(§4.12.4)を示している場合、式名がXによって直接的または間接的に囲まれた内部クラス、またはX (§15.27)によって含まれるラムダ式のいずれかにあると、コンパイル時にエラーが発生します。

これらのルールの実質的な効果は、(i)参照の場合、ローカル変数、仮パラメータまたは例外パラメータは、スコープ内で宣言されたネストされたクラスまたはインタフェースからのみ参照できることです。静的コンテキスト内にない、(ii)変数宣言への参照から内部(非static)クラスのチェーンがあり、(iii)変数がfinalまたは実質的にfinalである。 また、ラムダ式からの参照では、変数をfinalまたは実質的にfinalにする必要があります。

宣言で、単純式の前に確実に割り当てられるfinal変数が宣言されている場合、名前の意味はその変数の値です。 それ以外の場合、式名の意味は宣言で宣言された変数です。

代入コンテキスト、呼出しコンテキストまたはキャスト・コンテキストに式名が表示されている場合、式名の型は、取得変換後のフィールド、ローカル変数またはパラメータの宣言型です(§5.1.10)。

それ以外の場合、式名の型は、フィールド、ローカル変数またはパラメータの宣言された型です。

つまり、式名が"右側"と表示される場合、そのタイプは変換の取得の対象となります。 式名が"左側"で表示される変数の場合、そのタイプは変換の取得の対象ではありません。

例 6.5.6.1-1.  単純な式名

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

このプログラムでは、ivおよびfへの割当てで左側として使用される名前は、ローカル変数i、フィールドvおよびfの値(ffinal変数であるため、変数fではない)を示します。 したがって、この例では、最後の割当ての左側に変数がないため、コンパイル時にエラーが発生します。 エラーのある割当てが削除されると、変更済コードをコンパイルでき、出力が生成されます:

1 2 3

例 6.5.6.1-2.  インスタンス変数への参照

class Test {
    static String a;
    String b;

    String concat1() {
        return a + b;
    }

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

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

        I.Matcher matcher = new I.Matcher();
        matcher.check();
        return matcher.match(a, b);
    }
}

フィールドaおよびbは、クラスTestの本体全体でスコープ内にあります。 ただし、concat2メソッドの静的コンテキスト、またはTestの内部クラスではないネスト・クラスMatcherの宣言でbという名前を使用することは不正です。


例 6.5.6.1-3.  ローカル変数および仮パラメータへの参照

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

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

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

            static void checkFirst() {
                Runnable r = new Runnable() {
                    public void run() {
                        if (first == null) {  // compile-time error
                            throw new IllegalArgumentException();
                        }
                    }
                };
                r.run();
            }
        }

        final Checker c = new Checker();
        c.checkFirst();
        for (int i = 1; i < args.length; i++) {
            Runnable r = () -> {
                c.checkWhitespace(i);  // compile-time error
                c.checkFlag(i);  // compile-time error
            };
        }
    }
}

仮パラメータargsは、メソッドmainの本体全体でスコープ内にあります。argsは実質的にfinalであるため、argsという名前はローカル・クラスCheckerのインスタンス・メソッドcheckWhitespaceで使用できます。 ただし、ローカル・クラスCheckercheckFlagメソッドの静的コンテキストでargsという名前を使用することは不正です。

ローカル変数firstは、メソッドmainの残りの本体のスコープ内にあります。firstも事実上finalです。 ただし、checkFirstで宣言された匿名クラスはCheckerの内部クラスではないため、匿名クラス本体でfirstという名前を使用することは不正です。 (ラムダ式は静的コンテキストで発生するため、checkFirstの本体内のラムダ式は同様にfirstを参照できません。)

ローカル変数cは、メソッドmainの本体の最後の数行のスコープ内にあり、finalと宣言されているため、ラムダ式の本体で名前cを使用できます。

ローカル変数iは、forループ全体でスコープ内にあります。 ただし、iは事実上finalではないため、ラムダ式の本体にiという名前を使用することは不正です。


6.5.6.2. 修飾式名

式名の形式がQ.Idの場合、Qはすでにパッケージ名、タイプ名または式名として分類されています。

Qがパッケージ名の場合、コンパイル時にエラーが発生します。

Qがクラス型を指定する型名の場合、次のようになります。

  • Idという名前のフィールドであるクラス・タイプのアクセス可能なメンバー(§6.6)が1つしかない場合、コンパイル時にエラーが発生します。

  • それ以外の場合、1つのアクセス可能なメンバー・フィールドがクラス変数でない(つまり、staticが宣言されていない)場合、コンパイル時にエラーが発生します。

  • それ以外の場合、クラス変数がfinalとして宣言されている場合、Q.Idはクラス変数の値を示します。

    Q.Idの型は、取得変換後のクラス変数の宣言型です(§5.1.10)。

    Q.Idが、値ではなく変数を必要とするコンテキストに存在する場合、コンパイル時にエラーが発生します。

  • それ以外の場合、Q.Idはクラス変数を示します。

    Q.Idの型は、取得変換後のクラス変数の宣言型です(§5.1.10)。

    この句は、enum定数(§8.9)の使用をカバーしています。これは、これらの定数には常に対応するfinalクラス変数があるためです。

Qがインタフェース・タイプを指定するタイプ名の場合:

  • Idという名前のフィールドであるインタフェース・タイプのアクセス可能なメンバーが1つしかない場合、コンパイル時にエラーが発生します。

  • それ以外の場合、Q.Idはフィールドの値を示します。

    Q.Idの型は、取得変換後のフィールドの宣言型です(§5.1.10)。

    Q.Idが、値ではなく変数を必要とするコンテキストに存在する場合、コンパイル時にエラーが発生します。

Qが式名の場合、Tを式Qの型にします。

  • Tが参照型でない場合、コンパイル時にエラーが発生します。

  • Idという名前のフィールドであるT型のアクセス可能なメンバーが1つしかない場合、コンパイル時にエラーが発生します。

  • それ以外の場合、このフィールドが次のいずれかの場合:

    • インタフェース・タイプのフィールド

    • クラス型のfinalフィールド(クラス変数またはインスタンス変数のいずれか)

    • 配列型のfinalフィールドlength(§10.7)

    Q.Idは、変数を必要とするコンテキストにフィールドが存在し、フィールドが間違いなく未割当ての空白のfinalフィールドである場合を除き、フィールドの値を示します。この場合、このフィールドは変数を生成します。

    Q.Idの型は、取得変換後のフィールドの宣言型です(§5.1.10)。

    値ではなく変数を必要とするコンテキストにQ.Idが存在し、Q.Idで示されるフィールドが確実に割り当てられている場合は、コンパイル時にエラーが発生します。

  • それ以外の場合、Q.Idは変数、クラス変数またはインスタンス変数のいずれかであるクラスTのフィールドIdを示します。

    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のメソッドではないため、2つのコンパイル時エラーが発生します。


例 6.5.6.2-2. タイプ名を使用した式の修飾

式名は型名で修飾できますが、一般的な型では修飾できません。 その結果、パラメータ化された型を介してクラス変数にアクセスすることはできません。 たとえば、次のコードがあるとします。


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

次の代入は不正です:


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

代わりに、次の内容を記述します。


Foo.classVar = 91;

これにより、Javaプログラミング言語が意味のある方法で制限されることはありません。 型パラメータは静的変数の型では使用できないため、パラメータ化された型の型引数は静的変数の型に影響を与えません。 したがって、表現力は失われません。 型名FooはRAW型のように見えますが、実際にはRAW型ではなく、静的メンバーにアクセスする非汎用型Fooの名前です(§6.1)。 raw型を使用しないため、未チェックの警告はありません。


6.5.7.  メソッド名の意味

MethodNameとして分類される名前の意味は、次のように決定されます。

6.5.7.1.  単純なメソッド名

メソッド呼出し式のコンテキストに単純なメソッド名が表示されます(§15.12)。 単純なメソッド名は、呼び出されるメソッドの名前を指定する単一のUnqualifiedMethodIdentifierで構成されます。 メソッド呼出しのルールでは、UnqualifiedMethodIdentifierがメソッド呼出しの時点でスコープ内にあるメソッドを示す必要があります。 ルールは、静的コンテキスト(§8.1.3)で発生するインスタンス・メソッドへの参照、またはインスタンス・メソッドを宣言するクラスまたはインタフェースの内部クラス以外のネストされたクラスまたはインタフェース、またはインスタンス・メソッドがメンバーであるクラスの初期構成コンテキスト(§8.8.7)への参照も禁止します(§15.12.3)。

例 6.5.7.1-1.  単純なメソッド名

次のプログラムは、起動するメソッドを決定する際のスコープ指定のロールを示しています。

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

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

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

呼出しf1(0)の場合、スコープ内にあるメソッドはf1という1つのみです。 これはメソッドTest.f1(int)で、その宣言は匿名クラス宣言を含むTestの本体全体でスコープ内にあります。匿名クラス宣言にはf1という名前のメンバーがないため、§15.12.1ではクラスTestで検索が選択されます。 最終的に、Test.f1(int)は解決されます。

呼出しf2(0)では、f2という名前の2つのメソッドがスコープ内にあります。 まず、メソッドSuper.f2(String)の宣言は、匿名クラス宣言全体でスコープ内にあります。 次に、メソッドTest.f2(int)の宣言は、匿名クラス宣言を含むTestの本体全体でスコープ内にあります。 (各宣言が宣言された時点で、もう一方がスコープ内にないため、どちらの宣言も他方を影付けしないことに注意してください。)§15.12.1では、f2という名前のメンバーがあるため、クラスSuperでの検索が選択されます。 ただし、Super.f2(String)f2(0)には適用されないため、コンパイル時にエラーが発生します。 クラスTestは検索されないことに注意してください。

呼出しf3(0)の場合、f3という名前の3つのメソッドがスコープ内にあります。 1番目と2番目のメソッドSuper.f3(String)およびSuper.f3(int,int)の宣言は、匿名クラス宣言全体でスコープ内にあります。 第三に、メソッドTest.f3(int)の宣言は、匿名クラス宣言を含むTestの本体全体にわたってスコープ内にあります。§15.12.1は、クラスSuperf3という名前のメンバーがあるため、このクラスで検索することを選択します。 ただし、Super.f3(String)およびSuper.f3(int,int)f3(0)に適用できないため、コンパイル時にエラーが発生します。 クラスTestは検索されないことに注意してください。

字句的に囲むスコープの前にネストされたクラスのスーパークラス階層を検索することを選択することは、コンブ・ルール(§15.12.1)と呼ばれます。


6.6.  アクセス制御

Javaプログラミング言語は、アクセス制御のメカニズムを提供し、パッケージまたはクラスのユーザーが、そのパッケージまたはクラスの実装の不要な詳細に依存しないようにします。 アクセスが許可されている場合、アクセスされたエンティティはアクセス可能であるとみなされます。

アクセス可能性は、コンパイル時に判断される静的プロパティです。これは、型および宣言の修飾子にのみ依存します。

修飾名は、パッケージ、クラス、インタフェース、型パラメータおよび参照型のメンバーにアクセスする手段です。 このようなメンバーの名前がコンテキスト(§6.5.1)から修飾型名(パッケージ、クラス、インタフェースまたは型パラメータのメンバーを示す)または修飾式名(参照型のメンバーを示す)として分類される場合、アクセス制御が適用されます。

たとえば、単一型インポート宣言では修飾型名(§7.5.1)が使用されるため、import宣言を含むコンパイル・ユニットから名前付きクラスまたはインタフェースにアクセスできる必要があります。 別の例として、クラス宣言ではスーパークラス型(§8.1.5)の修飾型名が使用される可能性があるため、名前付きクラスにはアクセス可能である必要があります。

いくつかの明らかな式は、§6.5.1のコンテキスト分類から「欠落」しています: Primaryでのフィールド・アクセス(§15.11.1)、Primaryでのメソッド呼出し(§15.12)、Primaryを介したメソッド参照(§15.13)、および修飾クラス・インスタンス作成でのインスタンス化されたクラス(§15.9)。 これらの各式は、§6.2の理由により、名前ではなく識別子を使用します。 したがって、メンバー(フィールド、メソッド、クラスまたはインタフェース)に対するアクセス制御は、フィールド・アクセス式、メソッド呼出し式、メソッド参照式および修飾クラス・インスタンス作成式によって明示的に適用されます。 (フィールドに対するアクセスは、接尾辞式として出現する修飾名で示される場合もあります。)

また、多くの文および式では、型名のみで表現されない型を使用できます。 たとえば、クラス宣言では、パラメータ化された型(§4.5)を使用してスーパークラス型を示すことができます。 パラメータ化された型は修飾された型名ではないため、クラス宣言では、示されたスーパークラスに対してアクセス制御を明示的に実行する必要があります。 したがって、§6.5.1TypeNameを分類するためのコンテキストを提供する文および式のほとんどは、独自のアクセス制御チェックを実行します。

パッケージ、クラス、インタフェースまたは型パラメータのメンバーにアクセスする以外に、クラスのコンストラクタへのアクセスの問題があります。 コンストラクタが明示的または暗黙的に呼び出されたときに、アクセス制御をチェックする必要があります。 したがって、アクセス制御は、コンストラクタ呼出し(§8.8.7.1)およびクラス・インスタンス作成式(§15.9.3)によってチェックされます。 このようなチェックが必要となるのは、§6.5.1には、(名前ではなく、間接的にコンストラクタを参照する)コンストラクタ呼出しの記述がなく、修飾されていないクラス・インスタンス作成式によって示されるクラスと、そのクラスのコンストラクタの違いが認識されないためです。 また、コンストラクタには修飾名がないため、修飾された型名の分類中にチェックされたアクセス制御を信頼することはできません。

アクセシビリティは、非表示およびメソッドのオーバーライド(§8.4.8.1)を含む、クラス・メンバーの継承(§8.2)に影響します。

6.6.1. アクセシビリティの決定

  • 最上位のクラスまたはインタフェース(§7.6)がpublicとして宣言され、モジュールによってエクスポートされるパッケージのメンバーである場合、クラスまたはインタフェースに次のコードからアクセスできます。クラスまたはインタフェースが宣言されているコンパイル・ユニットが他のモジュール(§7.3)から見える場合、パッケージがエクスポートされる別のモジュール内の同じモジュールおよび任意のコード。

  • 最上位のクラスまたはインタフェースがpublicとして宣言され、モジュールによってエクスポートされないパッケージのメンバーである場合は、同じモジュール内のどのコードでもクラスまたはインタフェースにアクセスできます。

  • 最上位のクラスまたはインタフェースがパッケージ・アクセスで宣言されている場合、そのクラスまたはインタフェースは宣言されているパッケージ内からのみアクセスできます。

    アクセス修飾子なしで宣言された最上位のクラスまたはインタフェースは、暗黙的にパッケージ・アクセスを持ちます。

  • クラス、インタフェース、型パラメータ、参照型、またはクラスのコンストラクタのメンバー(クラス、インタフェース、フィールドまたはメソッド)は、(i)クラス、インタフェース、型パラメータまたは参照型がアクセス可能であり、(ii)メンバーまたはコンストラクタがアクセスを許可するように宣言されている場合にのみアクセス可能です。

    • メンバーまたはコンストラクタがpublicと宣言されている場合は、アクセスが許可されます。

      アクセス修飾子を持たないインタフェースのすべてのメンバーは、暗黙的にpublicです。

    • それ以外の場合、メンバーまたはコンストラクタがprotectedと宣言されていると、次のいずれかがtrueの場合にのみアクセスが許可されます。

      • メンバーまたはコンストラクタへのアクセスは、protectedメンバーまたはコンストラクタが宣言されているクラスを含むパッケージ内から行われます。

      • §6.6.2に記載されているとおり、アクセスは正しい。

    • それ以外の場合、メンバーまたはコンストラクタがパッケージ・アクセスで宣言されている場合、アクセスは、クラス、インタフェース、型パラメータまたは参照型が宣言されているパッケージ内からアクセスが発生したときにのみ許可されます。

      アクセス修飾子なしで宣言されたクラス・メンバーまたはコンストラクタは、暗黙的にパッケージ・アクセスを持ちます。

    • それ以外の場合、メンバーまたはコンストラクタはprivateとして宣言されます。 アクセスが許可されるのは、次のいずれかが当てはまる場合のみです。

      • アクセスは、メンバーまたはコンストラクタの宣言を包含する最上位クラスまたはインタフェースの本体内から行われます。

      • アクセスは、メンバーの宣言を包含する最上位のクラスまたはインタフェースのpermits句で行われます。

      • アクセスは、メンバーの宣言を囲むトップ・レベル・レコード・クラスのレコード・コンポーネント・リストで行われます。

  • 配列タイプは、その要素タイプがアクセス可能である場合にのみアクセス可能です。

例6.6-1.  アクセス制御

次の2つのコンパイル・ユニットについて考えてみます。

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で2つのクラス型を宣言する。

  • クラス・タイプPointVecは、publicではなく、パッケージpointspublicインタフェースの一部ではなく、パッケージ内の他のクラスでのみ使用できます。

  • クラス型Pointpublicとして宣言され、他のパッケージで使用できます。 これは、パッケージpointspublicインタフェースの一部です。

  • クラスPointのメソッドmovegetXおよびgetYpublicとして宣言されるため、Point型のオブジェクトを使用するすべてのコードで使用できます。

  • フィールドxおよびyprotectedとして宣言され、クラスPointのサブクラスでのみパッケージpointsの外部でアクセス可能であり、それらにアクセスしているコードによって実装されているオブジェクトのフィールドである場合にのみアクセス可能になります。

    protectedアクセス修飾子がアクセスを制限する例については、§6.6.2を参照してください。


例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およびypublicではないため、パッケージ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;
}

2つのクラスがコンパイル単位で宣言されます。 クラス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を使用またはインポートできません。このpoints.PointListpublicとして宣言されていないため、パッケージpointsの外部ではアクセスできません。


例6.6-4. パッケージ・アクセスによるフィールド、メソッドおよびコンストラクタへのアクセス

アクセス修飾子publicprotectedまたは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); }
}

その後、別のパッケージ内のサブクラスは、同じシグネチャ(§8.4.2)および戻り型を持つ、無関係なmoveメソッドを宣言できます。 元の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);
    }
}

PointmovePlusPointmoveによってオーバーライドされないため、PointのメソッドmoveAlsoPlusPointのメソッドmoveをコールしません。 したがって、PlusPointからsuper.moveコールを削除し、テスト・プログラムを実行する場合:

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

正常に終了します。 PointmovePlusPointmoveによってオーバーライドされた場合、このプログラムは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++; }
}

プライベート・メンバーIDmasterIDおよびsetMasterIDは、クラスPointの本体内でのみ使用できます。 Point宣言の本体外の修飾名、フィールド・アクセス式またはメソッド呼出し式からはアクセスできません。

privateコンストラクタを使用する例は、§8.8.10を参照してください。


6.6.2. protectedアクセスの詳細

オブジェクトのprotectedメンバーまたはコンストラクタは、そのオブジェクトの実装を担当するコードによってのみ宣言されるパッケージの外部からアクセスできます。

6.6.2.1. protectedメンバーへのアクセス

Cを、protectedメンバーが宣言されているクラスにします。 アクセスは、Cのサブクラス Sの本体内でのみ許可されます。

サブクラス Sは、クラス Cのオブジェクトの実装を担当するとみなされます。 Cのアクセシビリティーに応じて、SCと同じパッケージ、または Cと同じモジュールの異なるパッケージ、またはまったく異なるモジュールのパッケージで宣言できます。

また、インスタンス・フィールドまたはインスタンス・メソッドへのアクセスは、修飾名、フィールド・アクセス式(§15.11)、メソッド呼出し式(§15.12)またはメソッド参照式(§15.13)の形式に基づいて許可されます。

  • (i) ExpressionName.Idまたは TypeName.Id形式の修飾名、または(ii) Primary.Id形式のフィールドアクセス式によるアクセスの場合、修飾タイプが Sまたは Sのサブクラスである場合にのみ、インスタンスフィールド Idへのアクセスが許可されます。

    修飾タイプは、ExpressionNameまたはPrimaryのタイプ、またはTypeNameで示されるタイプです。

  • アクセスが(i)ExpressionName.Id(...)TypeName.Id(...)またはPrimary.Id(...)の形式のメソッド呼出し式、または(ii)ExpressionName :: IdPrimary :: IdまたはReferenceType :: Idの形式のメソッド参照式によるものである場合、修飾型がSまたはSのサブクラスである場合にのみ、インスタンス・メソッドIdへのアクセスが許可されます。

    修飾タイプは、ExpressionNameまたはPrimaryのタイプ、またはTypeNameまたはReferenceTypeで示されるタイプです。

protectedメンバーへのアクセスの詳細は、2005年10月のJournal of Object TechnologyのAlessandro CoglioによるChecking Access to Protected Members in the Java Virtual Machineを参照してください。

6.6.2.2. protectedコンストラクタへのアクセス

Cprotectedコンストラクタが宣言されるクラスとし、Sprotectedコンストラクタの使用が宣言される最も内側のクラスにします。 次に、

  • アクセスがスーパークラス・コンストラクタ呼出しsuper(...)または修飾されたスーパークラス・コンストラクタ呼出しE.super(...) (Eプライマリ式)によって行われる場合、アクセスが許可されます。

  • アクセスが匿名クラス・インスタンス作成式new C(...){...}または修飾された匿名クラス・インスタンス作成式E.new C(...){...} (Eプライマリ式)によるものである場合、アクセスが許可されます。

  • アクセスが単純なクラス・インスタンス作成式new C(...)、または修飾クラス・インスタンス作成式E.new C(...) (Eプライマリ式、またはメソッド参照式C :: new (CClassType)によるものである場合、アクセスは許可されません。 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メソッドで発生します。パラメータpprotectedメンバーxおよびyにはアクセスできません。これは、Point3d(フィールドxおよびyへの参照が発生するクラス)がPointのサブクラス(xおよびyが宣言されるクラス)であるのに対し、Pointの実装には関係しないためです(パラメータpのタイプ)。 メソッドdelta3dは、そのパラメータqprotectedメンバーにアクセスできます。これは、クラスPoint3dPointのサブクラスであり、Point3dの実装に関与するためです。

メソッドdeltaは、そのパラメータをPoint3dにキャスト(§5.5§15.16)しようとしましたが、実行時にpのクラスがPoint3dでない場合、このキャストは失敗し、例外が発生します。

コンパイル時エラーは、メソッドwarpでも発生します。パラメータaprotectedメンバーzにはアクセスできません。これは、クラスPoint(フィールドzへの参照が発生するクラス)がPoint3dの実装(パラメータaの型)に関与しているのに対し、Point3dのサブクラス(zが宣言されるクラス)ではないためです。


6.7.  完全修飾名および正規名

すべてのプリミティブ型、名前付きパッケージ、トップ・レベル・クラスおよびトップ・レベル・インタフェースには、完全修飾名があります。

  • プリミティブ型の完全修飾名は、そのプリミティブ型のキーワード(byteshortcharintlongfloatdoubleまたはboolean)です。

  • 名前付きパッケージのサブパッケージではない名前付きパッケージの完全修飾名は、単純名です。

  • 別の名前付きパッケージのサブパッケージである名前付きパッケージの完全修飾名は、包含パッケージの完全修飾名、.、その後にサブパッケージの単純(メンバー)名で構成されます。

  • 名前のないパッケージで宣言されている最上位クラスまたは最上位インタフェースの完全修飾名は、クラスまたはインタフェースの単純名です。

    特に、これは、コンパクト・コンパイル・ユニット(§7.3)によって暗黙的に宣言されるトップ・レベル・クラスに完全修飾名(ホスト・システム(§8.1.8)によって決定される単純な名前)があることを意味します。

  • 名前付きパッケージで宣言されるトップ・レベル・クラスまたはトップ・レベル・インタフェースの完全修飾名は、パッケージの完全修飾名、.、クラスまたはインタフェースの単純名で構成されます。

各メンバー・クラス、メンバー・インタフェースおよび配列型には、完全修飾名がある可能性があります

  • 別のクラスまたはインタフェースCのメンバー・クラスまたはメンバー・インタフェースMは、Cに完全修飾名がある場合のみ、完全修飾名を持ちます。

    この場合、Mの完全修飾名は、Cの完全修飾名、.Mの単純名で構成されます。

  • 配列型に完全修飾名があるのは、その要素型に完全修飾名がある場合のみです。

    その場合、配列型の完全修飾名は、配列型のコンポーネント型の完全修飾名に続けて「[]」で構成されます。

ローカル・クラス、ローカル・インタフェース、または匿名クラスに完全修飾名は付けられません。

すべてのプリミティブ型(名前付きパッケージ、トップ・レベル・クラスおよびトップ・レベル・インタフェース)には、正規名があります。

  • すべてのプリミティブ型、名前付きパッケージ、トップ・レベル・クラスおよびトップ・レベル・インタフェースで、正規名は完全修飾名と同じです。

各メンバー・クラス、メンバー・インタフェースおよび配列型は、正規名を持つ可能性があります

  • 別のクラスまたはインタフェースCで宣言されたメンバー・クラスまたはメンバー・インタフェースMは、Cに正規名がある場合のみ、正規名を持ちます。

    この場合、Mの正規名は、Cの正規名、.Mの単純名で構成されます。

  • 配列型に正規名があるのは、その要素型に正規名がある場合のみです。

    その場合、配列型の正規名は、配列型のコンポーネント型の正規名に続けて「[]」で構成されます。

ローカル・クラス、ローカル・インタフェース、または匿名クラスに正規名はありません。

例6.7-1.  完全修飾名

  • long型の完全修飾名はlongです。

  • パッケージjava.langは、パッケージjavaのサブパッケージlangであるため、完全修飾名はjava.langです。

  • クラスObjectの完全修飾名は、パッケージjava.langで定義され、java.lang.Objectです。

  • インタフェースEnumerationの完全修飾名は、パッケージjava.utilで定義され、java.util.Enumerationです。

  • 型"array of double"の完全修飾名は"double[]"です。

  • Stringの配列の配列」型の完全修飾名は「java.lang.String[][][][]」です。

コードでは、次のようになります。

package points;
class Point    { int x, y; }
class PointVec { Point[] vec; }

Point型の完全修飾名は"points.Point"、PointVec型の完全修飾名は"points.PointVec"、PointVecクラスのフィールドvecの型の完全修飾名は"points.Point[]"です。


例6.7-2.  完全修飾名および 正規名

完全修飾名と正規名の違いは、次のようなコードで確認できます:

package p;
class O1 { class I {} }
class O2 extends O1 {}

p.O1.Ip.O2.Iは両方とも、メンバー・クラスIを示す完全修飾名ですが、p.O1.Iのみが正規名です。