文字列テンプレート(プレビュー)

Java®言語仕様バージョン21.0.2+10-LTS-54への変更

このドキュメントでは、Java SE 21のプレビュー機能である文字列テンプレートをサポートするためのJava言語仕様の変更について説明します。この機能の概要は、JEP 430を参照してください。

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

変更ログ:

2023-05-26: 編集上のマイナーな変更。

2023-03-29: SimpleProcessorおよびStringProcessorインタフェースの削除。

2023-03-23: 新しいパッケージ組織を反映した名前の変更。

2023-03-01: その他のマイナーな変更。

2023-02-22: フィードバックに従ったマイナーな変更。

2023-02-09: 第3稿。様々な編集変更に加え、その他の重要な変更には次のものがあります。

2022-11-15: 第2稿。字句と構文の文法に関する主な変更。テンプレート用に新しい用語を導入しました。

2022-01-20: 初稿リリース。

第2章: 文法

2.1 コンテキスト自由文法

コンテキスト自由文法は、多数のプロダクションで構成されます。各プロダクションには、左側非終端と呼ばれる抽象記号があり、右側に1つ以上の非終端および終端記号のシーケンスがあります。文法ごとに、指定したアルファベットから終端記号が描画されます。

目標シンボルと呼ばれる単一の特徴のある非終端文字で構成される文から始まるコンテキスト自由文法では、言語、つまり、シーケンス内の非終端文字を、非終端が左側になるプロダクションの右側と繰り返し置き換えることから得られる、終端記号の一連のシーケンスを指定します。

一部の文法はあいまいです。つまり、目標シンボルから始まり、終端記号の同じシーケンスで終わるプロダクションを適用する方法は複数あります。あいまいさを解決するには、すべての代替に対してプロダクションを適用するという特定の方法を1つ選ぶか、他のコンテキスト情報を考慮に入れる必要があります。

2.2 字句文法

Javaプログラミング言語の字句文法3で紹介します。この文法は、終端記号としてUnicode文字セットの文字を使用しています。これは、Unicode文字のシーケンス(3.1)が入力要素のシーケンス(3.2)にどのように変換されるかを表す、目標シンボルInput (3.5)から始まる一連のプロダクションを定義するものです。

空白(3.6)とコメント(3.7)を破棄したこれらの入力要素は、Javaプログラミング言語の構文文法の終端記号を形成し、トークン(3.5)と呼ばれます。これらのトークンは、Javaプログラミング言語の識別子(3.8)、キーワード(3.9)、リテラル(3.10)、セパレータ(3.11)、および演算子(3.12)を含みます

字句文法はあいまいであり、このようなあいまいさを解決する方法(3.5)はいくつかの規則によって決まります。

2.3 構文文法

Javaプログラミング言語の構文文法については、第4章、6章から10章、14章、および15章を参照してください。この文法には、終端記号として字句文法で定義されたトークンがあります。目標シンボルCompilationUnit (7.3)から始まる一連のプロダクションを定義し、トークンのシーケンスが構文的に正しいプログラムを形成する方法を説明します。

一部の場所では、構文文法の特定のプロダクションが、字句文法(3.5)のあいまいさを解決するためのコンテキストを提供します。

便宜上、構文文法は第19章にまとめられています。

第2章の他の部分は変更されていません。

第3章: 字句構造

この章では、Javaプログラミング言語の字句構造について説明します。

プログラムはUnicode (3.1)で記述されますが、ASCII文字のみを使用するUnicode文字を含めるためにUnicodeエスケープ(3.3)を使用できるように、字句変換(3.2)が可能です。行の終了文字は、一貫した行番号を維持しながら既存のホスト・システムの各種規則をサポートできるように定義(3.4)されています。

字句変換によって生成されるUnicode文字は、空白(3.6)、コメント(3.7)、およびトークンである入力要素(3.5)のシーケンスに還元されます。トークンは、構文文法の識別子(3.8)、キーワード(3.9)、リテラル(3.10)、セパレータ(3.11)、および演算子(3.12)、およびフラグメント(3.13)です。

3.1 Unicode

プログラムはUnicode文字セット(1.7)を使用して記述されます。この文字セットとその関連文字エンコーディングに関する情報については、<https://www.unicode.org/>をご覧ください。

Java SEプラットフォームでは、Unicode Standardが展開されるたびに追跡されます。特定のリリースで使用されるUnicodeの正確なバージョンは、クラスCharacterのドキュメントで指定されます。

Unicode Standardは、本来は固定幅の16ビット文字エンコーディングとして設計されました。その後、16ビット以上の表現を必要とする文字を許容するように変更されています。有効なコード・ポイントの範囲は現在、16進数のU+n表記でU+0000からU+10FFFFです。コード・ポイントがU+FFFFよりも大きい文字は補助文字と呼ばれます。16ビット単位のみを使用して文字の範囲全体を表すために、Unicode StandardではUTF-16というエンコーディングを定義しています。このエンコーディングでは、補足文字は16ビット・コード・ユニットのペアとして表現され、最初のコード・ユニットは上位サロゲート範囲(U+D800からU+DBFF)から、2番目のコード・ユニットは下位サロゲート範囲(U+DC00からU+DFFF)から取得されます。U+0000からU+FFFFの範囲の文字の場合、コード・ポイントとUTF-16コード・ユニットの値は同じです。

Javaプログラミング言語は、UTF-16エンコーディングを使用した16ビット・コード単位のシーケンスでテキストを表します。

Java SEプラットフォームの一部のAPIは、主にCharacterクラスにあり、32ビット整数を使用してコード・ポイントを個々のエンティティとして表します。Java SEプラットフォームには、16ビット表現と32ビット表現を変換するメソッドが用意されています。

この仕様では、表現が関連しているコード・ポイントおよびUTF-16コード・ユニット、そして表現がディスカッションに関連しない汎用用語文字を使用します。

コメント(3.7)、識別子(3.8)、および文字リテラルの内容、文字列リテラル、およびテキスト・ブロックおよびテンプレート(3.10.43.10.53.10.63.13)を除き、プログラム内のすべての入力要素(3.5)は、ASCII文字(またはASCII文字になるUnicodeエスケープ(3.3))からのみ形成されます。

ASCII (ANSI X3.4)は、情報交換用米国標準コード(American Standard Code for Information Interchange)です。Unicode UTF-16エンコーディングの最初の128文字はASCII文字です。

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

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

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

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

空白(3.6)およびコメント(3.7)は、隣接している場合に別の方法でトークン化される可能性があるトークンを区切るために役立ちます。

たとえば、入力文字-および=は、間に空白またはコメントがない場合にのみ、演算子トークン-= (3.12)を形成できます。別の例では、10文字の入力文字staticvoidは単一の識別子トークンを形成しますが、11文字の入力文字static void (ASCII SP文字がcvの間にある)は空白で区切られたキーワード・トークンstaticvoidのペアを形成します。

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

Inputプロダクションはあいまいです。つまり、入力文字の一部のシーケンスには、入力文字を入力要素に還元する(つまり、入力文字をトークン化する)方法が複数あります。あいまいさは次のように解決されます。

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

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

class Empty {
}

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

3.10 リテラル

3.10.7 エスケープ・シーケンス

文字リテラル、文字列リテラル、およびテキスト・ブロック、およびテンプレートのフラグメント(3.10.43.10.53.10.63.13)内で、エスケープ・シーケンスにより、Unicodeエスケープ(3.3)を使用せずに、一部のグラフィック以外の文字をはじめ、一重引用符、二重引用符、バックスラッシュ文字を表現できます。

EscapeSequence:
\ b * (バックスペースBS、Unicode \u0008)*
\ s * (スペースSP、Unicode \u0020)*
\ t * (水平タブHT、Unicode \u0009)*
\ n * (改行LF、Unicode \u000a)*
\ f * (フォーム・フィードFF、Unicode \u000c)*
\ r * (復帰CR、Unicode \u000d)*
\ LineTerminator * (行の継続、Unicode表現なし)*
\ " * (二重引用符"、Unicode \u0022)*
\ ' * (一重引用符'、Unicode \u0027)*
\ \ * (バックスラッシュ\、Unicode \u005c)*
OctalEscape * (8進数、Unicode \u0000\u00ff)*
OctalEscape:
\ OctalDigit
\ OctalDigit OctalDigit
\ ZeroToThree OctalDigit OctalDigit #
OctalDigit:
(次のうちの1つ)
0 1 2 3 4 5 6 7
ZeroToThree:
(次のうちの1つ)
0 1 2 3

前述のOctalDigitプロダクションは3.10.1で得ています。8進数のエスケープは、Cとの互換性のために用意されていますが、Unicode値\u0000\u00FFしか表現できないため、通常はUnicodeエスケープの方が優先されます。

エスケープ・シーケンス内でバックスラッシュに続く文字がLineTerminatorまたはASCII bstnfr"'\01234567ではない場合、コンパイル時にエラーが発生します。

文字リテラル、文字列リテラル、またはテキスト・ブロック、またはテンプレートのフラグメント内のエスケープ・シーケンスは、\および末尾の文字を、EscapeSequence文法のUnicodeエスケープが示す単一文字に置き換えることによって解釈されます。行の継続エスケープ・シーケンスには、対応するUnicodeエスケープがないため、これは何にも置き換えられずに解釈されます。

行の継続エスケープ・シーケンスは、テキスト・ブロック内には出現する可能性がありますが、文字リテラルまたは文字列リテラル内ではそれぞれ、LineTerminatorが許可されないため出現できません。

文字シーケンス\{はエスケープ・シーケンスではありませんが、テンプレート(3.13)に出現する際に特別な意味を持ちます。

3.13 フラグメント

テンプレート(15.8.6)は、文字列リテラルまたはテキスト・ブロックに似ていますが、1つ以上の埋込み式が含まれます。埋込み式とは、先頭に文字シーケンス\{、文末に文字}が付加された式です。

フラグメントは、テンプレートの式以外の部分を表します。

Fragment:
StringTemplateBegin
StringTemplateMid
StringTemplateEnd
TextBlockTemplateBegin
TextBlockTemplateMid
TextBlockTemplateEnd
StringTemplateBegin:
" StringFragment \{
StringTemplateMid:
} StringFragment \{
StringTemplateEnd:
} StringFragment "
StringFragment:
{ StringCharacter }
TextBlockTemplateBegin:
""" { TextBlockWhiteSpace } LineTerminator TextBlockFragment \{
TextBlockTemplateMid:
} TextBlockFragment \{
TextBlockTemplateEnd:
} TextBlockFragment """
TextBlockFragment:
{ TextBlockCharacter }

便宜上、ここでは3.10.5および3.10.6の次のプロダクションを示します。

StringCharacter:
InputCharacter (ただし、"または\ではない)
EscapeSequence
TextBlockWhiteSpace:
WhiteSpace (ただし、LineTerminatorではない)
LineTerminator:
「改行」とも呼ばれるASCII LF文字
「復帰」とも呼ばれるASCII CR文字
ASCII LF文字が後に続くASCII CR文字
TextBlockCharacter:
InputCharacter (ただし、\ではない)
EscapeSequence
LineTerminator

フラグメントのcontentは次のように定義されます。

これは、StringTemplateBeginStringTemplateMid、またはStringTemplateEndトークンの内容に行の終終了文字(3.4)が表示されるときのコンパイル時のエラーです。

TextBlockTemplateBeginTextBlockTemplateMid、またはTextBlockTemplateEndトークンの内容は、次の手順を適用するとさらに変換されます。

テンプレートは文字列リテラル(およびテキスト・ブロック)に似ていますが、入力文字のシーケンスが構文的に正しい文字列リテラルと構文的に正しいテンプレートの両方を形成できないという意味では、あいまいではありません。これは、テンプレートには少なくとも1つの埋込み式が含まれている必要がありますが、埋込み式の先頭に付けるシーケンス\{が、文字列リテラル(またはテキスト・ブロック)の有効なエスケープ・シーケンスではないためです。

ただし、フラグメント・プロダクションでは、それ以外のトークン・プロダクション(3.5)によるあいまいさが発生します。このようなあいまいさは次のように解決されます。

たとえば、18文字の入力文字のシーケンス" \ { n e w i n t [ ] { 4 2 } } "を考えてみます。最初の3つの入力文字がStringTemplateBeginに還元されます。次の12個の入力文字は、トークンKeyword (new)、Keyword (int)、Separator ([)、Separator (])、Separator ({)、およびLiteral (42)に還元されます。シーケンス内の次の入力文字}は、あいまいさを生じさせます。これはSeparatorに還元するか、次の}および"入力文字とともにStringTemplateEndに還元できます。構文文法では配列作成式のArrayInitializer(15.10.1)のコンテキストが提供されるため、前述のルールにより、入力文字}Separatorに還元されます。残りの}および"の入力文字は、StringTemplateEndに還元されます。

第7章: パッケージおよびモジュール

7.3 コンパイル・ユニット

CompilationUnitは、Javaプログラムの構文文法(2.3)の目標シンボル(2.1)です。これは、次のプロダクションによって定義されます。

CompilationUnit:
OrdinaryCompilationUnit
ModularCompilationUnit
OrdinaryCompilationUnit:
[PackageDeclaration] {ImportDeclaration} {TopLevelClassOrInterfaceDeclaration}
ModularCompilationUnit:
{ImportDeclaration} ModuleDeclaration

通常コンパイル・ユニットは3つの部分で構成され、それぞれがオプションです。

モジュラ・コンパイル・ユニットmodule宣言(7.7)で構成され、オプションで、その前にimport宣言が含められます。import宣言を使用すると、このモジュールおよび他のモジュールのパッケージ内のクラスおよびインタフェースや、クラスおよびインタフェースのstaticメンバーを、module宣言内でそれらの単純名を使用して参照できます。

すべてのコンパイル単位は暗黙的に次のものをインポートします。

  1. package宣言の直後の各コンパイル・ユニットの先頭に宣言import java.lang.*;が出現するものとして、事前定義済パッケージjava.langで宣言されているそれぞれのpublicクラスまたはインタフェース。
  1. package宣言の直後の各コンパイル・ユニットの先頭に宣言import static java.lang.StringTemplate.STR;が出現するものとして、事前定義済クラスStringTemplateで宣言されている静的メンバーSTR

その結果、暗黙的にインポートされたそれらすべてのクラスおよびインタフェースクラス、インタフェースおよび静的フィールドの名前をそれぞれのコンパイル・ユニットで単純名として使用できます。

ホスト・システムによって、観察可能なコンパイル・ユニットが特定されます。ただし、事前定義済のパッケージjavaとそのサブパッケージlangおよびio内のコンパイル・ユニットは除きます(これらはすべて、常に観察可能です)。

セクション7.3の他の部分は変更されていません。

7.5 インポート宣言

7.5.3 単一静的インポート宣言

単一静的インポート宣言では、特定の単純名を持つアクセス可能なすべてのstaticメンバーをクラスまたはインタフェースからインポートします。これにより、単一静的インポート宣言が出現するコンパイル・ユニットのモジュール、クラスおよびインタフェース宣言内でstaticメンバーを単一名で使用できるようになります。

SingleStaticImportDeclaration:
import static TypeName .Identifier ;

TypeNameは、クラスまたはインタフェースの正規名(6.7)である必要があります。

クラスまたはインタフェースは、名前付きパッケージのメンバーであるか、最も外側にある字句的な包含クラスまたはインタフェース宣言(8.1.3)が名前付きパッケージのメンバーであるクラスまたはインタフェースのメンバーである必要があり、そうでない場合はコンパイル時にエラーが発生します。

名前付きクラスまたはインタフェースがアクセス可能でない場合(6.6)、コンパイル時にエラーが発生します。

Identifierは、名前付きクラスまたはインタフェースの少なくとも1つのstaticメンバーに名前を付ける必要があります。その名前を持つstaticメンバーがないか、名前付きメンバーがすべてアクセス不可能である場合は、コンパイル時にエラーが発生します。

1つの単一静的インポート宣言で、同じ名前を持つ複数のフィールド、クラスまたはインタフェース、あるいは同じ名前とシグネチャを持つ複数のメソッドをインポートできます。このことが発生するのは、名前付きクラスまたはインタフェースが、すべて同じ名前を持つ複数のフィールド、メンバー・クラス、メンバー・インタフェースまたはメソッドをそれ自体のスーパー・タイプから継承する場合です。

これは単一静的インポート宣言で、すでに暗黙的にインポートされているstaticメンバーの冗長インポートに使用できます。

同じコンパイル・ユニット内の2つの単一静的インポート宣言が同じ単純名のクラスまたはインタフェースをインポートしようとすると、コンパイル時にエラーが発生します。ただし、2つのクラスまたはインタフェースが同じである場合は除きます(この場合、重複する宣言は無視されます)。

単純名がxであるクラスまたはインタフェースが単一静的インポート宣言によってインポートされ、単純名がxである最上位クラスまたはインタフェース(7.6)もコンパイル・ユニットで宣言される場合、コンパイル時にエラーが発生します。

単純名がxであるクラスまたはインタフェースをインポートする単一静的インポート宣言と、単純名がxであるクラスまたはインタフェースをインポートする単一型インポート宣言(7.5.1)の両方がコンパイル・ユニットに含まれる場合、コンパイル時にエラーが発生します。ただし、2つのクラスまたはインタフェースが同じである場合は除きます(この場合、重複する宣言は無視されます)。

7.5.4 オンデマンド静的インポート宣言

オンデマンド静的インポート宣言を使用すると、名前付きクラスまたはインタフェースのアクセス可能なすべてのstaticメンバーを必要に応じてインポートできます。

StaticImportOnDemandDeclaration:
import static TypeName . * ;

TypeNameは、クラスまたはインタフェースの正規名(6.7)である必要があります。

クラスまたはインタフェースは、名前付きパッケージのメンバーであるか、最も外側にある字句的な包含クラスまたはインタフェース宣言(8.1.3)が名前付きパッケージのメンバーであるクラスまたはインタフェースのメンバーである必要があり、そうでない場合はコンパイル時にエラーが発生します。

名前付きクラスまたはインタフェースがアクセス可能でない場合(6.6)、コンパイル時にエラーが発生します。

これはオンデマンド静的インポート宣言で、すでに暗黙的にインポートされているstaticメンバーの冗長インポートに使用できます。

同じコンパイル・ユニット内の2つ以上のオンデマンド静的インポート宣言によって、同じクラスまたはインタフェースの名前が指定されることがあります。この場合、そのような宣言が1つのみ存在する場合と同様の結果になります。

セクション7.5.4の他の部分は変更されていません。

第12章: 実行

12.5 新しいクラス・インスタンスの作成

クラス・インスタンス作成式(15.9)の評価によってクラスがインスタンス化されると、新しいクラス・インスタンスが明示的に作成されます。

次の状況では、新しいクラス・インスタンスが暗黙的に作成される場合があります。

このようなそれぞれの状況は、クラス・インスタンスの作成プロセスの一部として、指定された引数(ない場合もある)で呼び出される特定のコンストラクタ(8.8)を識別します。

新しいクラス・インスタンスが作成されるときは、常に、クラスで宣言されたすべてのインスタンス変数とクラスの各スーパークラスで宣言されたすべてのインスタンス変数(隠すことができるすべてのインスタンス変数を含む)のための空き領域を確保して、新しいクラス・インスタンスにメモリー領域が割り当てられます(8.3)。

オブジェクトのメモリーを割り当てるための空き領域が不十分である場合、OutOfMemoryErrorによってクラス・インスタンスの作成が突然完了します。それ以外の場合は、スーパークラスで宣言された変数を含め、新しいオブジェクトのすべてのインスタンス変数がデフォルト値(4.12.5)に初期化されます。

新しく作成されたオブジェクトへの参照が結果として返される直前に、指定されたコンストラクタが処理され、次の手順に従って新しいオブジェクトが初期化されます。

  1. コンストラクタの引数を、このコンストラクタ呼出しのために新しく作成されたパラメータ変数に割り当てます。

  2. このコンストラクタが(thisを使用して)同一クラス内の別のコンストラクタの明示的なコンストラクタ呼出し(8.8.7.1)で始まる場合、その引数を評価し、この同じ5つのステップを使用して、コンストラクタの再帰的な呼出しを処理します。コンストラクタの呼出しが突然完了する場合、この手順は同じ理由で突然完了します。そうでない場合は、ステップ5から続行します。

  3. このコンストラクタは、(thisを使用する)同一クラス内の別のコンストラクタの明示的なコンストラクタ呼出しからは始まりません。このコンストラクタがObject以外のクラス用の場合、このコンストラクタは、(superを使用する)スーパークラス・コンストラクタの明示的または暗黙的な呼出しから始まります。これら5つのステップを使用して、スーパークラス・コンストラクタを再帰的に呼び出す引数とプロセスを評価します。コンストラクタの呼出しが突然完了する場合、この手順は同じ理由で突然完了します。それ以外の場合は、ステップ4に進みます。

  4. このクラスのインスタンス・イニシャライザおよびインスタンス変数イニシャライザを実行し、インスタンス変数イニシャライザの値を対応するインスタンス変数に左から右へ順に割り当て、クラスのソース・コードにテキストで表示されるようにします。これらのイニシャライザのいずれかを実行して例外が発生した場合、それ以上のイニシャライザは処理されず、この手順が同じ例外で突然完了します。それ以外の場合は、ステップ5に進みます。

  5. このコンストラクタの残りの本体を実行します。例外が突然完了する場合、この手順は同じ理由で突然完了します。それ以外の場合、この手順は正常に完了します。

C++とは異なり、Javaプログラミング言語では、新しいクラス・インスタンスの作成中にメソッド・ディスパッチに対するルール変更は指定されません。オブジェクトの初期化中、サブクラスでオーバーライドされるメソッドが呼び出された場合、新しいオブジェクトが完全に初期化される前でも、これらのオーバーライド・メソッドが使用されます。

例12.5-1.インスタンス作成の評価

class Point {
    int x, y;
    Point() { x = 1; y = 1; }
}
class ColoredPoint extends Point {
    int color = 0xFF00FF;
}
class Test {
    public static void main(String[] args) {
        ColoredPoint cp = new ColoredPoint();
        System.out.println(cp.color);
    }
}

ここでは、ColoredPointの新しいインスタンスが作成されます。最初に、フィールドxyおよびcolorを保持するために、新しいColoredPointに領域が割り当てられます。これらのフィールドはすべてデフォルト値(この場合は、各フィールドが0)に初期化されます。次に、引数のないColoredPointコンストラクタが最初に呼び出されます。ColoredPointはコンストラクタを宣言しないため、次の形式のデフォルトのコンストラクタが暗黙的に宣言されます。

ColoredPoint() { super(); }

このコンストラクタは、引数なしでPointコンストラクタを呼び出します。Pointコンストラクタはコンストラクタの呼出しで始まらないため、Javaコンパイラでは、引数なしのスーパークラス・コンストラクタの暗黙的な呼出しが次のような記述で指定されます。

Point() { super(); x = 1; y = 1; }

したがって、引数をとらないObjectのコンストラクタが呼び出されます。

クラスObjectにはスーパークラスがないため、再帰がここで終了します。次に、Objectのインスタンス・イニシャライザおよびインスタンス変数イニシャライザが呼び出されます。次に、引数をとらないObjectのコンストラクタの本体が実行されます。このようなコンストラクタはObjectで宣言されないため、Javaコンパイラは、このような特殊なケースでは次のようなデフォルトのコンストラクタを指定します。

Object() { }

このコンストラクタの実行は効果がないまま返されます。

次に、クラスPointのインスタンス変数のすべてのイニシャライザが実行されます。この場合、xおよびyの宣言では初期化式を指定しないため、この例のステップでは何のアクションも必要ありません。その後、Pointコンストラクタの本体が実行され、x1に、y1に設定されます。

次に、クラスColoredPointのインスタンス変数のイニシャライザが実行されます。このステップでは、値0xFF00FFcolorに割り当てます。最後に、ColoredPointコンストラクタの残りの本体(superの呼出し後の部分)が実行されます。本体の残りの部分には文がないため、それ以上のアクションは不要なまま初期化が完了します。

例12.5-2.インスタンス作成時の動的ディスパッチ

class Super {
    Super() { printThree(); }
    void printThree() { System.out.println("three"); }
}
class Test extends Super {
    int three = (int)Math.PI;  // That is, 3
    void printThree() { System.out.println(three); }

    public static void main(String[] args) {
        Test t = new Test();
        t.printThree();
    }
}

このプログラムでは、次の出力が生成されます。

0
3

これは、クラスSuperのコンストラクタでのprintThreeの呼出しによって、クラスSuperprintThreeの定義が呼び出されるのではなく、クラスTestprintThreeのオーバーライド定義が呼び出されることを示しています。したがって、このメソッドは、Testのフィールド・イニシャライザが実行される前に実行されます。このため、最初の値の出力は0、つまりTestのフィールドthreeが初期化されるデフォルト値になります。その後、メソッドmainprintThreeを呼び出すと、printThreeの同じ定義が呼び出されますが、その時点でインスタンス変数threeのイニシャライザが実行されているため、値3が出力されます。

第15章: 式

15.8 Primary式

Primary式には、最も単純な種類の式のほとんどが含まれ、ここからリテラル、オブジェクト作成、フィールド・アクセス、メソッド呼出し、メソッド参照、および配列アクセス、およびテンプレート式など、他のすべての式が作成されます。カッコ付きの式も、構文的にはPrimary式として扱われます。

Primary:
PrimaryNoNewArray
ArrayCreationExpression
PrimaryNoNewArray:
Literal
ClassLiteral
this
TypeName . this
( Expression )
ClassInstanceCreationExpression
FieldAccess
ArrayAccess
MethodInvocation
MethodReference
TemplateExpression

Javaプログラミング言語の文法のこの部分は、2つの点が異例です。まず、ローカル変数やメソッド・パラメータの名前などの単純な名前はPrimary式と想定されます。技術的な理由から、名前は、接尾辞式が導入された少し後でPrimary式とともにグループ化されます(15.14)。

この技術的な理由は、トークン・ルックアヘッドが1つのみのJavaプログラムの左から右への解析が可能なことに関係します。式(z[3])および(z[])について考えてみます。1つ目はカッコで囲まれた配列アクセス(15.10.3)で、2つ目はキャストの先頭(15.16)です。先読み記号が[の時点で、左から右への解析によってzが終端ではない名前に還元されます。キャストのコンテキストでは、名前をPrimaryに還元する必要はありませんが、NamePrimaryの代替の1つであった場合は、[の後に続くトークンに2つのトークンを先読みせず、還元する必要があるのか(つまり、現在の状況がカッコで囲まれた配列アクセスかキャストになるか)を判断できません。ここに示した文法では、NamePrimaryを分離したままにし、他の特定の構文ルール(ClassInstanceCreationExpressionMethodInvocationArrayAccess、およびPostfixExpression向けのルール、識別子を直接使用するためFieldAccess、向けではない)で許可することによって問題を回避します。この戦略により、NamePrimaryとして処理するかどうかの問題は、さらに多くのコンテキストを調査できるようになるまで実質的に保留にされます。

2つ目の異常な特徴は、式"new int[3][3]"の潜在的な文法上のあいまいさが回避されることです。これは、Javaでは常に多次元配列の単一作成を意味しますが、文法的な適性がない場合、"(new int[3])[3]"と同じ意味であると解釈されることもあります。

このあいまいさは、Primaryに想定される定義をPrimaryおよびPrimaryNoNewArrayに分割すると排除できます。(これは、"dangling else"の問題を回避するためにStatementStatementStatementNoShortIf (14.5)に分割する場合と比較できます。)

15.8.1 字句リテラル

リテラル(3.10)は、変化しない固定の値を示します。

便宜上、ここでは3.10の次のプロダクションを示します。

Literal:
IntegerLiteral
FloatingPointLiteral
BooleanLiteral
CharacterLiteral
StringLiteral
TextBlock
NullLiteral

リテラルの型は次のように決定されます。

整数リテラル、浮動小数点リテラル、booleanリテラルまたは文字リテラルは、リテラルがソース・コード表現である値と評価されます。文字列リテラルまたはテキスト・ブロックは、3.10.5および3.10.6で指定されているクラスStringのインスタンスと評価されます。nullリテラルはnull参照と評価されます。

字句リテラルの評価は、常に正常に完了します。

15.8.6 テンプレート式

テンプレート式は、リテラル・テキストを式の値と組み合せる一般的な方法を提供します。テキストと式は、templateで指定されます。テキストと式の値を組み合せるタスクは、テンプレート・プロセッサに委任されます。

Stringへのテキストと値の単純な補間は、事前定義済のテンプレート・プロセッサSTR (7.3)から使用できます。他のテンプレート・プロセッサでは、テキストと値を任意の方法で組み合せて、Stringよりも高度な型の結果を生成できます。

TemplateExpression:
TemplateProcessor .TemplateArgument
TemplateProcessor:
Expression
TemplateArgument:
Template
StringLiteral
TextBlock
Template:
StringTemplate
TextBlockTemplate
StringTemplate:
StringTemplateBegin EmbeddedExpression
{ StringTemplateMid EmbeddedExpression } StringTemplateEnd
TextBlockTemplate:
TextBlockTemplateBegin EmbeddedExpression
{ TextBlockTemplateMid EmbeddedExpression } TextBlockTemplateEnd
EmbeddedExpression:
[ Expression ]

便宜上、ここでは3.13の次のプロダクションを示します。

StringTemplateBegin:
" StringFragment \{
StringTemplateMid:
} StringFragment \{
StringTemplateEnd:
} StringFragment "
StringFragment:
{ StringCharacter }
TextBlockTemplateBegin:
""" TextBlockFragment \{
TextBlockTemplateMid:
} TextBlockFragment \{
TextBlockTemplateEnd:
} TextBlockFragment """
TextBlockFragment:
{ TextBlockCharacter }

テンプレートは、文字列テンプレートまたはテキスト・ブロック・テンプレートのいずれかです。文字列テンプレート(それぞれがテキスト・ブロック・テンプレート)は、文字列リテラル(テキスト・ブロック)に似ていますが、1つ以上の埋込み式が含まれます。埋込み式とは、先頭に文字シーケンス\{、文末に文字}が付加された式です。文字シーケンス\{}の間に何も表示されない場合、埋込み式は暗黙的にnullリテラル(3.10.8)とみなされます。

n個の埋込み式(n>0)を含む文字列テンプレートは、n個の埋込み式を含むn+1個のフラグメントの代替インターリーブで構成されます。最初のフラグメントはStringTemplateBeginトークン(3.13)、次のn-1フラグメントはStringTemplateMidトークンであり、最後のフラグメントはStringTemplateEndトークンです。

一部の文字列テンプレートの内訳は次のとおりです。"\{42} is the answer."は、StringTemplateBeginトークン("\{)、式42 (整数リテラル)、StringTemplateEndトークン(} is the answer.")で構成されます。文字列テンプレート"The answer is \{x+y}!"は、StringTemplateBeginトークン("The answer is \{)、式x+yStringTemplateEndトークン(}!")で構成されます。文字列テンプレート"Hello \{name} from \{address.city},"は、StringTemplateBeginトークン("Hello \{)、式nameStringTemplateMidトークン(} from \{)、式address.cityStringTemplateEndトークン(},")で構成されます。最後に、文字列テンプレート"Customer name: \{}"は、StringTemplateBeginトークン("Customer name: \{)、(暗黙的な)式nullStringTemplateEndトークン(}")で構成されます。

n個の埋込み式(n>0)を含むテキスト・ブロック・テンプレートは、n個の埋込み式を含むn+1個のフラグメントの代替インターリーブで構成されます。最初のフラグメントはTextBlockTemplateBeginトークン(3.13)、次のn-1フラグメントはTextBlockTemplateMidトークンであり、最後のフラグメントはTextBlockTemplateEndトークンです。

テンプレートのフラグメント文字列は、埋込み式を囲むリテラル・テキストを表します。フラグメント文字列は次のように決定されます。

たとえば、文字列テンプレート"\{42} is the answer."のフラグメント文字列は、最初に空の文字列、次に文字列" is the answer."が続きます。文字列テンプレート"The answer is \{x+y}!"のフラグメント文字列は、文字列"The answer is "の後に"!"が続きます。文字列テンプレート"Hello \{name} from \{address.city},"のフラグメント文字列は、文字列"Hello "" from "","と続きます。最後に、文字列テンプレート"Customer name: \{}"のフラグメント文字列は、最初に文字列"Customer name: "の後に空の文字列が続きます。

テキスト・ブロック・テンプレートのフラグメント文字列

"""
                Name:
                \{customerName}"""

最初はその内容が6文字のシーケンスN a m e : LF (付随する空白は削除されていることに注意してください)の文字列、その後に空の文字列が続きます。

TemplateProcessor式の型TPStringTemplate.Processorのサブタイプである必要があり、そうでない場合はコンパイル時エラーが発生します。TPStringTemplate.Processor<Result,Exc>のサブタイプで、ResultおよびExcがパラメータ化型の型引数の場合、テンプレート式の型はResultです。TPがRAW型StringTemplate.Processorのサブタイプである場合、テンプレート式の型はObjectです。

StringTemplate.Processor<R,E>は汎用関数型インタフェース(9.8)で、単一のabstractメソッドprocessには、StringTemplate仮パラメータ、戻り型R、型Ethrows句があります。

テンプレートに表示される埋込み式の型に制限はありません。

例15.8.6-1.単純なテンプレート

次の簡単な例では、すべてのコンパイル・ユニットに暗黙的にインポートされ、単純な文字列補間を実装するStringTemplatestaticメンバーSTRを使用します。

// A string template with simple embedded string variables
String firstName = "Joan";
String lastName  = "Smith";
String fullName  = STR."\{firstName} \{lastName}";

// A string template with embedded integer expressions
int x = 10, y = 20;
String s1 = STR."\{x} + \{y} = \{x + y}";

// Embedded expressions can invoke methods and access fields
String s2 = STR."You have a \{getOfferType()} waiting for you!";
String s3 = STR."Access at \{req.date} \{req.time} from \{req.ipAddress}";

// A text block template modeling an HTML element with
// embedded expressions
String title = "My Web Page";
String text  = "Hello, world";
String html = STR."""
        <html>
          <head>
            <title>\{title}</title>
          </head>
          <body>
            <p>\{text}</p>
          </body>
        </html>
        """;

// A text block template modeling a JSON value with
// embedded expressions
String name    = "Joan Smith";
String phone   = "555-123-4567";
String address = "1 Maple Drive, Anytown";
String json = STR."""
    {
        "name":    "\{name}",
        "phone":   "\{phone}",
        "address": "\{address}"
    }
    """;

実行時に、テンプレート式は次のように評価されます。

  1. TemplateProcessor式が評価されます。結果の値がnullの場合、NullPointerExceptionがスローされ、その理由からテンプレート式全体が突然完了します。TemplateProcessorの評価が突然完了する場合、テンプレート式全体が同じ理由で突然完了します。

  2. TemplateArgumentStringLiteralまたはTextBlockの場合、このステップの結果はStringTemplateのインスタンスで、引数TemplateArgumentを使用してstaticメソッドStringTemplate.ofを呼び出したものとして生成されます。

    TemplateArgumentTemplateの場合、埋込み式e1, ..., en (n > 0)が評価され、embedded values, v1, ..., vnが生成されます。埋込み式は、テンプレートに表示される左から右の順序で評価されます。埋込み式の評価が突然完了する場合、テンプレート式全体が同じ理由で突然完了します。

    それ以外の場合、このステップの結果は、次のプロパティを持つクラスのインスタンスへの参照になります。

    • StringTemplateインタフェースを実装したクラスであること。

    • 埋込み値v1, ..., vnを含むjava.util.Listのインスタンスをこの順序で返すインスタンス・メソッドvalues

    • テンプレートのフラグメント文字列だけを含むjava.util.Listのインスタンスを順に返すインスタンス・メソッドfragments

    • (1)テンプレートの正確なフラグメント文字列、および(2)埋込み値v1, ..., vnを最初のフラグメント文字列から順番に連結した厳密な代替インターリーブ文字列を返すクラス・インスタンスのインスタンス・メソッドinterpolate

  3. テンプレート式を評価した結果は、ステップ1の結果でメソッドprocessを呼び出したものとして、ステップ2の結果から指定された引数を使用して決定されます。このメソッド呼出しが突然完了する場合、テンプレート式全体が同じ理由で突然完了します。

つまり、テンプレート式の意味は次のとおりです。

e."..." // "..." is a valid string template

は、メソッド呼出し式の意味に相当します。

e.process(t)

ここでtは、テンプレート内で埋込み式のリテラル・テキストと値の両方をカプセル化するStringTemplateのインスタンスを表します。

例15.8.6-2.単純なテンプレート・プロセッサ。

StringTemplateinterpolateメソッドは、テンプレートのフラグメント文字列と埋込み値を連結して文字列を生成する便利な方法を提供します。次の例では、UPPERは指定されたテンプレートを補間しながら、すべての文字を大文字に変換します。

StringTemplate.Processor<String, RuntimeException> UPPER = (StringTemplate st) ->
    st.interpolate().toUpperCase();

String name = "Joan";
String result = UPPER."My name is \{name}";

これらの文の実行後、resultは文字列"MY NAME IS JOAN"で初期化されます。

例15.8.6-3.より複雑なテンプレート・プロセッサ。

より複雑なテンプレート・プロセッサでは、次の単純なプログラミング・パターンを使用できます。次の例では、MY_UPPER_STRINGSが(valuesメソッドから返された埋込み値を使用する)interpolateメソッドによって文字列結果を返す前に、(fragmentsメソッドによって返された)フラグメント文字列を大文字に変換します。

StringTemplate.Processor<String, RuntimeException> MY_UPPER_STRINGS = (StringTemplate st) -> {
    List<String> fragments = st.fragments()
        .stream()
        .map(String::toUpperCase)
        .toList();
    List<Object> values = st.values();
    return StringTemplate.interpolate(fragments, values);
};

String name = "Joan";
String result = MY_UPPER_STRINGS."My name is \{name}";

これらの文の実行後、resultは文字列"MY NAME IS Joan"で初期化されます。

次の例では、MY_UPPER_VALUESは、補間の前に(null値をすべて処理しながら)埋込み式を大文字の文字列に変換します。

StringTemplate.Processor<String, RuntimeException> MY_UPPER_VALUES = (StringTemplate st) -> {
    List<String> values = st.values()
        .stream()
        .map((o) -> (o==null)?"":o.toString().toUpperCase())
        .toList();
    return StringTemplate.interpolate(st.fragments(), values);
};

String title = null;
String firstName = "Joan";
String familyName = "Smith";
String result = MY_UPPER_VALUES."Welcome \{title}\{firstName} \{familyName}";

これらの文の実行後、resultは文字列"Welcome JOAN SMITH"で初期化されます。

例15.8.6-4.文字列を返さないテンプレート・プロセッサ

テンプレートを処理し、文字列以外の値を返すことができます。次の例では、JSONは、文字列ではなくクラスJSONObjectのインスタンスを返します。

StringTemplate.Processor<JSONObject, RuntimeException> JSON =
    (StringTemplate st) -> new JSONObject(st.interpolate());

String name    = "Joan Smith";
String phone   = "555-123-4567";
String address = "1 Maple Drive, Anytown";

JSONObject doc = JSON."""
    {
        "name":    "\{name}",
        "phone":   "\{phone}",
        "address": "\{address}"
    }
    """;

例15.8.6-6.例外をスローできるテンプレート・プロセッサ

テンプレートが要件を満たさない場合、特定のテンプレートを検証し、例外をスローすることが役立つ場合があります。

次の例では、JSON_VALIDATEが指定のテンプレートをクラスJSONObjectのインスタンスに変換しますが、まず中間補間文字列が(先頭または末尾の空白を無視し)、一致する中カッコ内で始まり、終了しているかをチェックします。これらのチェックのいずれかが失敗した場合はJSONExceptionがスローされます。それ以外の場合は対応するJSONObjectインスタンスが返されます。

StringTemplate.Processor<JSONObject, JSONException> JSON_VALIDATE = (StringTemplate st) -> {
    String stripped = st.interpolate().strip();
    if (!stripped.startsWith("{") || !stripped.endsWith("}")) {
        throws new JSONException("Missing brace");
    }
    return new JSONObject(stripped);
};

String name    = "Joan Smith";
String phone   = "555-123-4567";
String address = "1 Maple Drive, Anytown";
try {
    JSONObject doc = JSON_VALIDATE."""
        {
            "name":    "\{name}",
            "phone":   "\{phone}",
            "address": "\{address}"
        }
        """;
} catch (JSONException ex) {
    ...
}