このドキュメントでは、特に、コンテキスト・キーワード(以前は制限付き識別子および制限付きキーワードとして記述されていました)の識別において字句文法を適用するときのコンテキストの使用方法を明確にするために、Java言語仕様に加えられた変更について説明します。
Java SE 9以降、これらのキーワードに対しては2つのアプローチが試されてきました。1つ目は、構文文法の観点でどこに出現するかを記述することで、文字シーケンスがキーワードかどうかを決定する方法です。2つ目は、キーワードを識別子トークンとして扱うが、後で構文文法ではそれを(キーワードのように)文字どおり参照する方法です。
non-sealed
のような新しい形式のコンテキスト・キーワードの導入に伴い、2つ目のアプローチは意味をなさなくなりました(non-sealed
は単一の識別子ではなく、トークンのシーケンスです)。そのため、この改訂では、1つ目の方法で標準化しています。つまり、コンテキスト・キーワードは、構文文法でどこに出現するかに基づいて識別されます。詳細は、3.9を参照してください。
変更は、JLSの既存のセクションについて説明しています。新しいテキストはこのように示され、削除されたテキストはこのように示されます。必要に応じて、説明と考察が端の方にグレーのボックスで囲まれて記載されています。
第3章: 字句構造
3.2 字句変換
RAW Unicode文字ストリームは、次の3つの字句変換ステップを順番に適用することで、トークンのシーケンスに変換されます。
Unicode文字のRAWストリーム内にあるUnicodeエスケープ(3.3)の、対応するUnicode文字への変換。
\uxxxx
形式(xxxx
は16進数値)のUnicodeエスケープは、エンコーディングがxxxx
のUTF-16コード・ユニットを表します。この変換ステップによって、ASCII文字のみを使用してプログラムを表現できます。ステップ1の結果のUnicodeストリームの、入力文字および行終了文字(3.4)のストリームへの変換。
ステップ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文字がc
とv
の間にある)は空白で区切られたキーワード・トークンstatic
とvoid
のペアとして解釈されます。同様に、入力文字
a--b
はa
、--
およびb
として解釈されます。これは、文法的に正しいプログラムの一部ではありませんが、解釈a
、-
、-
およびb
であれば文法的に正しいプログラムの一部となります。他方で、入力文字a- -b
(ASCII SP文字が2つの-
文字の間にある)は、a
、-
、-
およびb
として解釈されます。
特定のオペレーティング・システムとの互換性のための特別な便宜的措置として、ASCII SUB文字(\u001a
またはcontrol-Z)は、エスケープされた入力ストリームの最後の文字である場合は無視されます。
結果の入力ストリーム内の2つのトークンx
およびy
について考えます。x
がy
より先行する場合、x
はy
の左側にあり、y
はx
の右側にあると言い表します。
たとえば、次の簡単なコードでは、
class Empty { }
}
トークンは、この2次元表現では{
トークンの左下に示されていますが、{
トークンの右側にあると言い表します。左と右という言葉の使用に関するこの取り決めにより、たとえば、バイナリ演算子の右側のオペランドや代入の左側と言い表すことができます。
3.8 識別子
識別子は、Javaの文字およびJavaの数字からなる無限長のシーケンスで、先頭はJavaの文字です。
- Identifier:
- Keyword、BooleanLiteral、NullLiteralのいずれでもない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つの問題:
「同じスペルである」は、コンテキストを考慮するかどうかについて不明確です。純粋に文字に着目すると、特定の識別子は、実際に特定のキーワードとスペルが同じになる場合があります。
プログラマが識別子を使用することを意図した箇所で誤ってキーワードを使用しても、必ずしもコンパイル時にエラーが表示されません。プログラムが期待と異なる方法で解釈される可能性もあります。(たとえば、クラス本体で、
Public Foo() { ... }
はメソッド宣言ですが、public Foo() { ... }
はコンストラクタ宣言です。)
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」を参照してください。
識別子の例は、次のとおりです。
String
i3
- αρετη
MAX_VALUE
isLetterOrDigit
識別子var
およびyield
は、一部のコンテキストでは許可されないため、制限付き識別子です。
この改訂されたアプローチでは、var
およびyield
はコンテキスト・キーワードです。var
およびyield
の使用に対する制限は同じままですが、制限付き識別子という用語は意味をなさなくなりました。制限付きとなるコンテキストでは、これらは識別子にはならないためです。
一部のコンテキストでは、コンテキスト・キーワード(3.9)の認識を促進するために、構文文法では識別子のサブセットの観点でプロダクションを定義することにより特定の識別子を許可しません。これらのサブセットは、次のように定義されています。
型識別子は、文字シーケンスvar
または文字シーケンスyield
ではない識別子です。
- TypeIdentifier:
var
またはyield
以外の識別子
型識別子はタイプ識別子は、型の宣言または使用を含む特定のコンテキストで使用されます。たとえば、クラスの名前はTypeIdentifierである必要があるため、var
またはyield
という名前のクラスを宣言することは不正になります(8.1)。
非修飾メソッド識別子は、文字列yield
でない識別子です。
- UnqualifiedMethodIdentifier:
yield
以外の識別子
この制限によって、yield
をyield
文(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つのキーワードを意図する場合は、空白またはコメントで区切る必要があります。
コンテキスト・キーワードと一致する他の文字シーケンスは、構文文法で次のコンテキストのいずれかに出現した場合にのみ、キーワードとして扱われます。
open
およびmodule
は、ModuleDeclaration (7.7)で終端として指定されたように出現する場合requires
、exports
、opens
、uses
、provides
、to
およびwith
は、ModuleDirectiveで終端として指定されたように出現する場合transitive
は、RequiresModifierで終端として指定されたように出現する場合ディレクティブ
requires transitive;
はRequiresModifierを使用しないため、この場合transitive
は識別子として解釈されます。var
は、LocalVariableType (14.4)またはLambdaParameterType (15.27.1)で終端として指定されたように出現する場合その他の多くのコンテキストでは、文字シーケンス
var
を識別子として使用しようとすると、var
は有効なTypeIdentifier (3.8)ではないため、エラーが発生します。yield
は、YieldStatement (14.21)で終端として指定されたように出現する場合その他の多くのコンテキストでは、文字シーケンス
yield
を識別子として使用しようとすると、yield
は有効なTypeIdentifierでも有効なUnqualifiedMethodIdentifierでもないため、エラーが発生します。
これらのルールは構文文法の詳細に依存しますが、Javaプログラミング言語のコンパイラは入力プログラムを完全に解析しなくてもそれらを実装できます。たとえば、コンテキスト・キーワードの有効な使用がキーワードとしてトークン化され、識別子の有効な使用が識別子としてトークン化されることがヒューリスティックで保証されているかぎり、ヒューリスティックを使用してトークナイザのコンテキスト状態を追跡できます。または、コンパイラでは常にコンテキスト・キーワードを識別子としてトークン化し、これらの識別子の特別な使用を認識する、より後の段階で処理されるようにできます。
実装によってどのようにあいまいさが解消されるかについては意図的に明言を避けています。これは実装の選択であって明示的に指定するものではないということは、前述の注記によって十分に明らかにされています。それでも、言語の進化に伴い、設計者は特定のコンテキストに新しいコンテキスト・キーワードを実装する影響について注意が必要になります。
場合によっては、様々な文字列が誤ってキーワードであるとみなされます。
これ以外の文字列として、open
、module
、requires
、transitive
、exports
、opens
、to
、uses
、provides
およびwith
の10種類の制限付きキーワードがあります。これらの文字列は、ModuleDeclaration、ModuleDirectiveおよびRequiresModifierプロダクションの端末として表示される場合のみ、キーワードとしてトークン化されます(7.7)。これらは、それ以外のすべての場所で識別子としてトークン化されます。これにより、制限付きキーワードの導入前に作成されたプログラムとの互換性を確保します。例外が1つあります。文字列transitive
は、ModuleDirectiveプロダクション内の文字列requires
のすぐ右側で、後ろにセパレータが続かないかぎり、キーワードとしてトークン化されます。この場合、これは識別子としてトークン化されます。
これらのルールは、前述の新しいルールに取り込まれます。
「指定されたように出現する場合」という言い換えにより、transitive
の特別な例外は必要なくなります。前述のように、文法でRequiresModifierが予期されていない場合には、transitive
はキーワードとして扱われる適切なコンテキストにありません。
3.12 演算子
ASCII文字で構成された38個のトークンが演算子です。
- 演算子:
- (次のうちの1つ)
= > < ! ~ ? : ->
== >= <= != && || ++ --
+ - * / & | ^ % << >> >>>
+= -= *= /= &= |= ^= %= <<= >>= >>>=
文字>
が型コンテキスト(4.11)に(つまり、構文文法のTypeまたはUnannType (4.1、8.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つのステップが必要です。
最初に、コンテキストにより、名前は構文的に7つのカテゴリ(ModuleName、PackageName、TypeName、ExpressionName、MethodName、PackageOrTypeNameまたはAmbiguousName)のいずれかに分類されます。
TypeNameおよびMethodNameは、それぞれTypeIdentifierおよびUnqualifiedMethodIdentifier (3.8)で示されるため、他の5つのカテゴリよりも表現は劣ります。
前者では文字シーケンスvar
およびyield
(3.8)が除外され、後者では文字シーケンスyield
が除外されます。この文は、新しいコンテキスト・キーワードが追加されると同期しなくなります。3.8の考察の繰返しになります。相互参照で十分です。
2番目に、そのコンテキストから最初にAmbiguousNameまたはPackageOrTypeNameとして分類された名前が、PackageName、TypeNameまたはExpressionNameに再分類されます。
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.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では匿名クラス宣言に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)。