コンテキスト・キーワード

Java®言語仕様バージョン16+37-2232への変更

このドキュメントでは、特に、コンテキスト・キーワード(以前は制限付き識別子および制限付きキーワードとして記述されていました)の識別において字句文法を適用するときのコンテキストの使用方法を明確にするために、Java言語仕様に加えられた変更について説明します。

Java SE 9以降、これらのキーワードに対しては2つのアプローチが試されてきました。1つ目は、構文文法の観点でどこに出現するかを記述することで、文字シーケンスがキーワードかどうかを決定する方法です。2つ目は、キーワードを識別子トークンとして扱うが、後で構文文法ではそれを(キーワードのように)文字どおり参照する方法です。

non-sealedのような新しい形式のコンテキスト・キーワードの導入に伴い、2つ目のアプローチは意味をなさなくなりました(non-sealedは単一の識別子ではなく、トークンのシーケンスです)。そのため、この改訂では、1つ目の方法で標準化しています。つまり、コンテキスト・キーワードは、構文文法でどこに出現するかに基づいて識別されます。詳細は、3.9を参照してください。

変更は、JLSの既存のセクションについて説明しています。新しいテキストはこのように示され、削除されたテキストはこのように示されます。必要に応じて、説明と考察が端の方にグレーのボックスで囲まれて記載されています。

第3章: 字句構造

3.2 字句変換

RAW Unicode文字ストリームは、次の3つの字句変換ステップを順番に適用することで、トークンのシーケンスに変換されます。

  1. Unicode文字のRAWストリーム内にあるUnicodeエスケープ(3.3)の、対応するUnicode文字への変換。\uxxxx形式(xxxxは16進数値)のUnicodeエスケープは、エンコーディングがxxxxのUTF-16コード・ユニットを表します。この変換ステップによって、ASCII文字のみを使用してプログラムを表現できます。

  2. ステップ1の結果のUnicodeストリームの、入力文字および行終了文字(3.4)のストリームへの変換。

  3. ステップ2の結果の入力文字および行終了文字のストリームの、入力要素(3.5)のシーケンスへの変換。これは、空白(3.6)およびコメント(3.7)が破棄された後で、構文文法(2.3)の終端記号であるトークン(3.5)を構成します。

一般的に、最長の変換が各ステップで使用され、このことは、その結果からは最終的に正しいプログラムが作成されず、別の字句変換によって正しいプログラムが作成される場合にも当てはまります。例外が1つあります。字句変換が型コンテキスト(4.11)に出現し、入力ストリームに2つ以上の連続する>文字があり、かつその後に>以外の文字が続く場合、各>文字は数値比較演算子>のトークンに変換される必要があります。3.3および3.5で説明するように、いくつかの例外があります。

入力文字a--bは、a--bとしてトークン化(3.5)されます。これは、文法的に正しいプログラムの一部ではありません(トークン化a--bは文法的に正しいプログラムの一部になることができます)。

>文字のルールがなければ、List<List<String>>などの型に含まれる2つの連続する>カッコは符号付き右シフト演算子>>としてトークン化される一方で、List<List<List<String>>>などの型に含まれる3つの連続する>カッコは符号なし右シフト演算子>>>としてトークン化されます。さらに悪いことに、List<List<List<List<String>>>>などの型に含まれる4つ以上の連続する>カッコのトークン化はあいまいになります。>>>および>>>トークンの様々な組合せが>>>>文字を表すためです。

例外は1つであるという以前の表現は大げさでした。

ステップ1で、文字シーケンス\\u1234は、2つではなく7つの個別の文字として扱われます(3.3)。これは適切ですが、最長の変換ルールに対する別の例外を示しています。

ステップ3で、コンテキスト・キーワードがハイフンでつながれる場合があり(現在はありませんが)、コンテキストによってはハイフンが個別のトークンとして扱われます。

あいまいさの扱いに関する考察は各セクションで行うほうが適切です。

>文字に関する考察は3.12に移動しました。

3.5 入力要素およびトークン

Unicodeエスケープ処理(3.3)の結果の入力文字および行終了文字と入力行認識(3.4)が入力要素のシーケンスに削減されます。

Input:
{InputElement} [Sub]
InputElement:
WhiteSpace
Comment
Token
Token:
Identifier
Keyword
Literal
Separator
Operator
Sub:
ASCII SUB文字、別名はcontrol-Z

空白またはコメント以外の入力要素はトークンです。トークンは構文文法(2.3)の終端記号です。

Inputプロダクションはあいまいです。つまり、入力文字および行終了文字の一部のシーケンスには、Inputプロダクションをシーケンスと一致させる方法が複数あります。あいまいさは次のように解決されます。

空白(3.6)およびコメント(3.7)は、隣接している場合に別の方法でトークン化される可能性があるトークンを区切るために役立ちます。たとえば、入力内のASCII文字-および=は、間に空白またはコメントがない場合にのみ、演算子トークン-= (3.12)を形成できます。

したがって、入力文字staticvoidは単一の識別子トークンとして解釈されますが、入力文字static void (ASCII SP文字がcvの間にある)は空白で区切られたキーワード・トークンstaticvoidのペアとして解釈されます。

同様に、入力文字a--ba--およびbとして解釈されます。これは、文法的に正しいプログラムの一部ではありませんが、解釈a--およびbであれば文法的に正しいプログラムの一部となります。他方で、入力文字a- -b (ASCII SP文字が2つの-文字の間にある)は、a--およびbとして解釈されます。

特定のオペレーティング・システムとの互換性のための特別な便宜的措置として、ASCII SUB文字(\u001aまたはcontrol-Z)は、エスケープされた入力ストリームの最後の文字である場合は無視されます。

結果の入力ストリーム内の2つのトークンxおよびyについて考えます。xyより先行する場合、xy左側にあり、yx右側にあると言い表します。

たとえば、次の簡単なコードでは、

class Empty {
}

}トークンは、この2次元表現では{トークンの左下に示されていますが、{トークンの右側にあると言い表します。左と右という言葉の使用に関するこの取り決めにより、たとえば、バイナリ演算子の右側のオペランドや代入の左側と言い表すことができます。

3.8 識別子

識別子は、Javaの文字およびJavaの数字からなる無限長のシーケンスで、先頭はJavaの文字です。

Identifier:
KeywordBooleanLiteralNullLiteralのいずれでもないIdentifierChars
IdentifierChars:
JavaLetter {JavaLetterOrDigit}
JavaLetter:
「Javaの文字」である任意のUnicode文字
JavaLetterOrDigit:
「Javaの文字または数字」である任意のUnicode文字

「Javaの文字」は、メソッドCharacter.isJavaIdentifierStart(int)がtrueを返す文字です。

「Javaの文字または数字」は、メソッドCharacter.isJavaIdentifierPart(int)がtrueを返す文字です。

「Javaの文字」には、大文字および小文字のASCIIのラテン文字A-Z (\u0041-\u005a)、a-z (\u0061-\u007a)、および歴史的理由によってASCIIのドル記号($または\u0024)とアンダースコア(_または\u005f)が含まれます。ドル記号は、機械的に生成されたソース・コードで使用するか、まれに、レガシー・システムで既存の名前にアクセスする場合のみ使用する必要があります。アンダースコアは、2つ以上の文字で構成された識別子には使用できますが、キーワードであるために1文字の識別子として使用することはできません。

「Javaの数字」には、ASCII数字0-9 (\u0030-\u0039)が含まれます。

文字および数字は、Unicode文字セット全体から取得できます。Unicode文字セットは、世界中で現在使用されているほとんどの作成スクリプトをサポートしています。これには、中国語、日本語および韓国語の大規模なセットが含まれます。これによりプログラマは、ネイティブ言語で作成されるプログラムで識別子を使用できるようになります。

識別子には、キーワード(3.9)、ブール・リテラル(3.10.3)またはnullリテラル(3.10.8)と同じスペル(Unicode文字列)を使用することはできません。使用すると、コンパイル時にエラーが発生します。

入力文字のシーケンスは、(特定のコンテキストで)キーワード(3.9)、ブール・リテラル(3.10.3)またはnullリテラル(3.10.8)を表す場合、識別子を表しません。

以前の言葉遣いに関する2つの問題:

2つの識別子が同一であるのは、無視できるモジュールを無視した後に、識別子が文字または数字ごとに同じUnicode文字を持つ場合のみです。無視できる文字は、メソッドCharacter.isIdentifierIgnorable(int)がtrueを返す文字です。外観が同じである識別子でも、依然として異なる場合があります。

たとえば、単一文字LATIN CAPITAL LETTER A (A\u0041)、LATIN SMALL LETTER A (a\u0061)、GREEK CAPITAL LETTER ALPHA (A\u0391)、CYRILLIC SMALL LETTER A (a\u0430)およびMATHEMATICAL BOLD ITALIC SMALL A (a\ud835\udc82)で構成された識別子はすべて異なります。

Unicodeの複合文字は、正規の同等の分解文字とは異なります。たとえば、LATIN CAPITAL LETTER A ACUTE (Á\u00c1)は、識別子内でLATIN CAPITAL LETTER A (A, \u0041)の直後にNON-SPACING ACUTE (´\u0301)が付いたものとは異なります。『The Unicode Standard』のセクション3.11「Normalization Forms」を参照してください。

識別子の例は、次のとおりです。

識別子varおよびyieldは、一部のコンテキストでは許可されないため、制限付き識別子です。

この改訂されたアプローチでは、varおよびyieldコンテキスト・キーワードです。varおよびyieldの使用に対する制限は同じままですが、制限付き識別子という用語は意味をなさなくなりました。制限付きとなるコンテキストでは、これらは識別子にはならないためです。

一部のコンテキストでは、コンテキスト・キーワード(3.9)の認識を促進するために、構文文法では識別子のサブセットの観点でプロダクションを定義することにより特定の識別子を許可しません。これらのサブセットは、次のように定義されています。

型識別子は、文字シーケンスvarまたは文字シーケンスyieldではない識別子です。

TypeIdentifier:
varまたはyield以外の識別子

型識別子はタイプ識別子、型の宣言または使用を含む特定のコンテキストで使用されます。たとえば、クラスの名前はTypeIdentifierである必要があるため、varまたはyieldという名前のクラスを宣言することは不正になります(8.1)。

非修飾メソッド識別子は、文字列yieldでない識別子です。

UnqualifiedMethodIdentifier:
yield以外の識別子

この制限によって、yieldyield文(14.21)で使用でき、互換性の理由から(修飾)メソッド名としても使用できます。

UnqualifiedMethodIdentifierは、単一の識別子のメソッドを参照する場合に使用されます。yieldという名前のメソッドの呼出しは、その呼出しをyield文と区別するために修飾する必要があります。

「型識別子」という正式な用語はこのセクションの外では1回のみ使用され(6.1)、「非修飾メソッド識別子」は使用されません。単に文法プロダクション名を使用するほうが単純です。

UnqualifiedMethodIdentifierに関する考察は、改訂の結果、TypeIdentifierに似たものとなり、設計の動機ではなく、制限がどこに適用されるかについて説明しています。(設計の動機は、前述の新しい紹介文に基づきます。)

3.9 キーワード

ASCII文字文字で構成された51個の文字シーケンスは、キーワードとして使用するために予約されており、識別子(3.8)として使用することはできません。同じくASCII文字で構成された別の12個の文字シーケンスは、出現するコンテキストによっては、キーワードとして解釈される可能性があります

_はASCII文字ではないことに注意してください。-も同様であり、これはnon-sealedのような一部のコンテキスト・キーワード内に出現することが予期されます。

キーワード:
ReservedKeyword
ContextualKeyword
ReservedKeyword:
(次のうちの1つ)
abstract continue for new switch
assert default if package synchronized
boolean do goto private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static void
class finally long strictfp volatile
const float native super while
_ (アンダースコア)
ContextualKeyword:
(次のうちの1つ)
exports opens to var
module provides transitive with
open requires uses yield

キーワードconstおよびgotoは、現在使用されていませんが、予約されています。これにより、これらのC++のキーワードがプログラムで誤って出現した場合にJavaコンパイラがより適切なエラー・メッセージを作成できるようになります。キーワード_ (アンダースコア)は、将来パラメータ宣言で使用できるように予約されています。

コンテキスト・キーワードと一致する文字シーケンスは、シーケンスの一部が直前または直後の文字と組み合せて異なるトークンを形成できる場合は、キーワードとして扱われません。

したがって、文字シーケンスopenmoduleは、ModuleDeclarationの最初にあっても、2つのコンテキスト・キーワードではなく、単一の識別子として解釈されます。2つのキーワードを意図する場合は、空白またはコメントで区切る必要があります。

コンテキスト・キーワードと一致する他の文字シーケンスは、構文文法で次のコンテキストのいずれかに出現した場合にのみ、キーワードとして扱われます。

これらのルールは構文文法の詳細に依存しますが、Javaプログラミング言語のコンパイラは入力プログラムを完全に解析しなくてもそれらを実装できます。たとえば、コンテキスト・キーワードの有効な使用がキーワードとしてトークン化され、識別子の有効な使用が識別子としてトークン化されることがヒューリスティックで保証されているかぎり、ヒューリスティックを使用してトークナイザのコンテキスト状態を追跡できます。または、コンパイラでは常にコンテキスト・キーワードを識別子としてトークン化し、これらの識別子の特別な使用を認識する、より後の段階で処理されるようにできます。

実装によってどのようにあいまいさが解消されるかについては意図的に明言を避けています。これは実装の選択であって明示的に指定するものではないということは、前述の注記によって十分に明らかにされています。それでも、言語の進化に伴い、設計者は特定のコンテキストに新しいコンテキスト・キーワードを実装する影響について注意が必要になります。

場合によっては、様々な文字列が誤ってキーワードであるとみなされます。

これ以外の文字列として、openmodulerequirestransitiveexportsopenstousesprovidesおよびwithの10種類の制限付きキーワードがあります。これらの文字列は、ModuleDeclarationModuleDirectiveおよびRequiresModifierプロダクションの端末として表示される場合のみ、キーワードとしてトークン化されます(7.7)。これらは、それ以外のすべての場所で識別子としてトークン化されます。これにより、制限付きキーワードの導入前に作成されたプログラムとの互換性を確保します。例外が1つあります。文字列transitiveは、ModuleDirectiveプロダクション内の文字列requiresのすぐ右側で、後ろにセパレータが続かないかぎり、キーワードとしてトークン化されます。この場合、これは識別子としてトークン化されます。

これらのルールは、前述の新しいルールに取り込まれます。

「指定されたように出現する場合」という言い換えにより、transitiveの特別な例外は必要なくなります。前述のように、文法でRequiresModifierが予期されていない場合には、transitiveはキーワードとして扱われる適切なコンテキストにありません。

3.12 演算子

ASCII文字で構成された38個のトークンが演算子です。

演算子:
(次のうちの1つ)
= > < ! ~ ? : ->
== >= <= != && || ++ --
+ - * / & | ^ % << >> >>>
+= -= *= /= &= |= ^= %= <<= >>= >>>=

文字>が型コンテキスト(4.11)に(つまり、構文文法のTypeまたはUnannType (4.18.3)の一部として)出現すると、隣接する>文字と組み合されて別の演算子を形成できる場合でも、常に>演算子として扱われます。

したがって、文字シーケンスList<List<String>>は、として出現すると、2つの>演算子になります。

>文字のこのルールがないと、List<List<String>>などの型に含まれる2つの連続する>カッコは符号付き右シフト演算子>>としてトークン化されます(3.5)。一方、List<List<List<String>>>などの型に含まれる3つの連続する>カッコは符号なし右シフト演算子>>>としてトークン化されます。さらに悪いことに、List<List<List<List<String>>>>などの型に含まれる4つ以上の連続する>カッコのトークン化はあいまいになります。>>>および>>>トークンの様々な組合せが>>>>文字を表すためです。

第6章: 名前

6.1 宣言

宣言により、エンティティがプログラムに導入され、このエンティティを参照するために名前内で使用できる識別子(3.8)が組み込まれます。この識別子には、導入されるエンティティがクラス、インタフェースまたは型パラメータである場合は型識別子TypeIdentifierであるという制約があります。

...

6.5 名前の意味の確認

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

ModuleName:
Identifier
ModuleName . Identifier
PackageName:
Identifier
PackageName . Identifier
TypeName:
TypeIdentifier
PackageOrTypeName . TypeIdentifier
PackageOrTypeName:
Identifier
PackageOrTypeName . Identifier
ExpressionName:
Identifier
AmbiguousName . Identifier
MethodName:
UnqualifiedMethodIdentifier
AmbiguousName:
Identifier
AmbiguousName . Identifier

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

6.5.7 メソッド名の意味

6.5.7.1 単純なメソッド名

単純なメソッド名はメソッド呼出し式(15.12)のコンテキストに出現します。単純なメソッド名は、呼び出されるメソッドの名前を示す単一のIdentifierUnqualifiedMethodIdentifierで構成されます。IdentifierUnqualifiedMethodIdentifierが、メソッド呼出しの時点でスコープ内にあるメソッドを示しているか、または単一静的インポート宣言かオンデマンド静的インポート宣言(7.5.37.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では匿名クラス宣言にf1という名前のメンバーがないため、クラス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つのメソッドがスコープ内になります。最初と2番目は、メソッドSuper.f3(String)およびSuper.f3(int,int)の宣言が匿名クラス宣言全体でスコープ内になります。3番目に、メソッドTest.f3(int)の宣言が匿名クラス宣言を含め、Test本体全体でスコープ内になります。15.12.1では、f3という名前のメンバーがあるためクラスSuperでの検索が選択されます。ただし、Super.f3(String)およびSuper.f3(int,int)f3(0)には適用できないため、コンパイル時にエラーが発生します。クラスTestは検索されないことに注意してください。

語彙的な包含スコープの前でネストされたクラスのスーパークラス階層の検索を選択することは、「コーム・ルール」と呼ばれます(15.12.1)。