名前のないパターンおよび変数(プレビュー)

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

このドキュメントでは、Java SE 21プレビュー機能である名前のないクラスおよび変数をサポートするためのJava言語仕様に対する変更について説明します。この機能の概要は、JEP:443を参照してください。

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

変更ログ:

2023-03-22: 初期仕様のドラフト。

第3章: 字句構造

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つ以上の文字で構成された識別子には使用できますが、キーワード(3.9)であるために1文字の識別子としては使用できません。

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

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

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」を参照してください。

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

トークン化のルール(3.5)により、識別子には、予約キーワード(3.9)、ブール・リテラル(3.10.3)またはnullリテラル(3.10.8)と同じスペル(Unicode文字列)を使用することはできません。ただし、プログラム内でのシーケンスの表示場所によって、入力文字列を識別子またはコンテキスト・キーワードのどちらでトークン化するかは、プログラム内の順序の表示場所によって決まるため、識別子がコンテキスト・キーワードと同じスペルになる場合があります。

コンテキスト・キーワードの認識を促進するために、構文文法(2.3)では識別子のサブセットのみ受け入れるようプロダクションを定義し、特定の識別子を許可しない場合があります。サブセットは、次のとおりです。

TypeIdentifier:
permitsrecordsealedvarまたはyieldではない識別子
UnqualifiedMethodIdentifier:
yield以外の識別子

TypeIdentifierはクラス、インタフェース、および型パラメータ(8.19.14.4)の宣言、および型への参照(6.5)に使用されます。たとえば、クラスの名前はTypeIdentifierである必要があるため、permitsrecordsealedvarまたはyieldという名前のクラスを宣言することは不正です。

UnqualifiedMethodIdentifierは、メソッド呼出し式が単純名(6.5.7.1)でメソッドを参照する場合に使用されます。yieldという用語はUnqualifiedMethodIdentifierから除外されるため、yieldという名前のメソッドの呼出しは、その呼出しをyield文と区別するために修飾する必要があります(14.21)。

3.9 キーワード

次の変更は、JEP 440 (レコード・パターン)およびJEP 441 (switchのパターン・マッチング)によるJLS変更が適用されていることを前提としています(JLS:JEP440+441)。

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

Keyword:
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 permits sealed var
module provides to when
non-sealed record transitive with
open requires uses yield
opens

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

キーワードstrictfpは廃止されています。新しいコードには使用しないでください。

キーワード_ (アンダースコア)は、将来パラメータ宣言で使用できるように予約されています。

キーワード_ (アンダースコア)は、識別子(6.1)のかわりに宣言で使用できます。

trueおよびfalseはキーワードではなく、booleanリテラルです(3.10.3)。

nullはキーワードではなく、nullリテラルです(3.10.8)。

入力文字から入力要素への還元(3.5)の際に、コンテキスト・キーワードと概念的に一致する入力文字のシーケンスは、次の両方の条件が満たされる場合にのみ、コンテキスト・キーワードに還元されます。

  1. シーケンスは、次のように、構文文法(2.3)の適切なコンテキストで指定された終端として認識されます。

    • moduleおよびopenについては、ModuleDeclaration (7.7)で終端として認識される場合。

    • exportsopensprovidesrequirestousesおよびwithについては、ModuleDirectiveで終端として認識される場合。

    • transitiveについては、RequiresModifierで終端として認識される場合。

      たとえば、シーケンスrequires transitive ;の認識では、RequiresModifierが使用されていないため、このtransitiveという用語はコンテキスト・キーワードではなく識別子に還元されます。

    • varについては、LocalVariableType (14.4)またはLambdaParameterType (15.27.1)で終端として認識される場合。

      その他のコンテキストでは、識別子としてvarを使用しようとすると、varTypeIdentifier (3.8)ではないためにエラーが発生します。

    • yieldについては、YieldStatement (14.21)で終端として認識される場合。

      その他のコンテキストでは、識別子としてyieldを使用しようとすると、yieldTypeIdentifierUnqualifiedMethodIdentifierのどちらでもないためにエラーが発生します。

    • recordについては、RecordDeclaration (8.10)で終端として認識される場合。

    • non-sealedpermitsおよびsealedについては、NormalClassDeclaration (8.1)またはNormalInterfaceDeclaration (9.1)で終端として認識される場合。

    • whenについては、Guard (14.11.1)で終端として認識される場合。

  2. シーケンスの直前または直後に、JavaLetterOrDigitと一致する入力文字がない。

一般に、ソースコードで誤って空白を省略すると、「可能なかぎり長い変換」ルール(3.2)によって、入力文字のシーケンスが識別子としてトークン化されます。たとえば、12文字の入力文字シーケンスp u b l i c s t a t i cは、予約キーワードのpublicおよびstaticとしてではなく、常に識別子のpublicstaticとしてトークン化されます。2つのトークンを使用する場合は、それらを空白またはコメントで区切る必要があります。

前述のルールは、「可能なかぎり長い変換」ルールと連動して、コンテキスト・キーワードが現れるコンテキスト内で直感的な結果を生成します。たとえば、11文字の入力文字シーケンスv a r f i l e n a m eは、通常、識別子varfilenameとしてトークン化されますが、ローカル変数宣言では、最初の3文字の入力文字は、前述のルールの最初の条件によって暫定的にコンテキスト・キーワードvarとして認識されます。ただし、シーケンス内の空白の不足を見落とすと、その次の8文字の入力文字を識別子filenameとして認識することによる混乱が生じます。(これは、シーケンスが異なるコンテキストで異なるトークン化を経ることを意味します。ほとんどのコンテキストでは識別子ですが、ローカル変数宣言ではコンテキスト・キーワードと識別子です)。したがって、2番目の条件により、直後の入力文字のfJavaLetterOrDigitであるという理由で、コンテキスト・キーワードvarの認識を防止します。そのため、シーケンスv a r f i l e n a m eは、ローカル変数宣言の識別子varfilenameとしてトークン化されます。

コンテキスト・キーワードの慎重な認識の別の例として、15文字の入力文字のシーケンスn o n - s e a l e d c l a s sについて考えてみます。このシーケンスは、通常、識別子non、演算子-および識別子sealedclassの3つのトークンに変換されますが、最初の条件が成立する通常のクラス宣言では、最初の10の入力文字は暫定的にコンテキスト・キーワードnon-sealedとして認識されます。シーケンスを3つの非キーワード・トークンではなく2つのキーワード・トークン(non-sealedclass)に解釈されることを回避し、classの前の空白を省略するプログラマに報酬を与えないようにするために、2番目の条件によってコンテキスト・キーワードの認識を防止します。そのため、シーケンスn o n - s e a l e d c l a s sは、クラス宣言では3つのトークンとしてトークン化されます。

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

第6章: 名前

6.1 宣言

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

宣言されたエンティティは、次のいずれかです。

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

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

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

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

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

汎用クラスまたはインタフェースの宣言(class C<T> ...またはinterface C<T> ...)は、Cという名前のクラスおよび型のファミリ: RAW型C、パラメータ化型C<Foo>、パラメータ化型C<Bar>などの両方を導入します。

非汎用コンテキストの1つとして次に示されている汎用性が重要ではない場所でCへの参照が発生した場合、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.69.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.114.20.3)

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

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

最初の12の非汎用コンテキストは、[6.5.1]内のTypeNameの最初の12の構文的コンテキストに対応しています。13番目の非汎用コンテキストは、修飾されたExpressionName (C.xなど)に、静的メンバー・アクセスを示すためのTypeName Cが含まれる可能性がある場所です。これらの13のコンテキスト内でTypeNameが共通して使用されていることが重要です。これは、これらのコンテキストには、型のファーストクラス未満の使用方法が含まれることを示します。対照的に、14番目と15番目の非汎用コンテキストではClassTypeが採用されています。これは、throwsおよびcatch句には、型がファーストクラスの方法でフィールド宣言などにあわせて使用されていることを示します。これら2つのコンテキストに非汎用としての特性が与えられるのは、例外型をパラメータ化できないという事実のためです(8.1.2)

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

命名規則

Java SEプラットフォームのクラス・ライブラリは、可能なかぎり、次の命名規則に従って選択された名前を使用しようとします。これらの命名規則は、コードをより読みやすくし、ある種の命名規則の競合を回避する上で役に立ちます。

Javaプログラミング言語で書かれたすべてのプログラムで、これらの命名規則を使用することをお薦めします。ただし、以前から使用されてきた慣例的な使用方法とは異なる場合、これらの命名規則に無条件に従う必要はありません。このため、たとえば、クラスjava.lang.Mathsinおよびcosメソッドには、ここで提案されている命名規則をこれらのメソッド名が無視する形になるとしても、これらは短く、動詞ではないため、数学的に慣例的な名前が使用されます。

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

プログラマは、広範に配布するパッケージに一意のパッケージ名を選択することにより、公開される2つのパッケージが同じ名前を持つ可能性を避けるべく策を講じる必要があります。これにより、パッケージを簡単かつ自動的にインストールおよびカタログ化できるようになります。このセクションでは、このような一意のパッケージ名を生成するために提案されている命名規則を規定します。一連のパッケージをローカルかつ日常的なパッケージ名から、ここで説明する一意の名前形式に変換する処理を自動的にサポートするために、Java SEプラットフォームを実装することをお薦めします。

一意のパッケージ名を使用しない場合、競合するパッケージの作成とはまったく異なる点でパッケージ名の競合が発生する可能性があります。この結果、ユーザーやプログラマでは解決が困難または不可能になる状況が生じるおそれがあります。クラスClassLoaderおよびModuleLayerを使用して、同じ名前を持つパッケージを相互に隔離できますが、その場合、パッケージのインタラクションが制限されても、単純なプログラムには透過的な方法では制限されません。

一意のパッケージ名を形成するには、最初にoracle.comなどのインターネット・ドメイン名を用意します(またはこのようなドメイン名を持つ組織に属します)。その後、この名前をコンポーネント別に反転させて、この例ではcom.oracleを取得します。次に、組織内でパッケージ名をより詳細に管理するために策定した命名規則を使用して、この名前をパッケージ名の接頭辞として使用します。このような命名規則では、特定のパッケージ名コンポーネントを部署、部門、プロジェクト、マシンまたはログイン名として指定する場合があります。

例6.1-1.一意のパッケージ名

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

一意のパッケージ名の最初のコンポーネントは常に、すべて小文字のASCII文字で書かれます。これは、comedugovmilnetまたはorgなどの最上位のドメイン名の1つ、またはISO標準3166に規定されている国を示す英語2文字のコードの1つにする必要があります。

場合によっては、インターネットのドメイン名が有効なパッケージ名ではないことがあります。次に、これらの状況に対処する上で提案される命名規則を示します。

モジュールの名前は、エクスポートされたプリンシパル・パッケージの名前に対応している必要があります。モジュールにこのようなパッケージがない場合、または慣習的な理由により、モジュール名をエクスポートされたパッケージの1つに対応していない名前にする必要がある場合、その名前は依然として、その作者が関連するインターネットのドメインを反転させた形から始まる必要があります。

例6.1-2.一意のモジュール名

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

パッケージまたはモジュール名の最初のコンポーネントは、識別子javaにしないでください。識別子javaから始まるパッケージまたはモジュール名は、Java SEプラットフォームのパッケージまたはモジュール用として予約されています。

パッケージまたはモジュールの名前は、パッケージまたはモジュールがインターネット上のどこに格納されているかを示すことを意図していません。たとえば、edu.cmu.cs.bovik.cheeseという名前のパッケージは必ずしも、ホストcmu.eduまたはcs.cmu.eduまたはbovik.cs.cmu.eduから取得できるわけではありません。一意のパッケージまたはモジュール名を生成するために提案される命名規則は、パッケージまたはモジュール名用として別のレジストリを作成するかわりに、広く知られる既存の一意の名前レジストリにパッケージおよびモジュールの命名規則を単に便乗させる方法です。

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

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

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

`ClassLoader`
SecurityManager
`Thread`
Dictionary
BufferedInputStream

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

型変数名

型変数名は、簡潔(可能であれば1文字)でありながら示唆に富む名前にする必要があります。また、小文字は含めないでください。これにより、型パラメータを一般的なクラスやインタフェースと区別しやすくなります。

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

任意の型を示す型パラメータが複数存在する場合、アルファベットでTの隣にある文字(Sなど)を使用する必要があります。また、異なる型変数を区別するために下付き数字(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;
}

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

メソッド名

メソッド名は、動詞または動詞句にし、大文字と小文字を混在させ、先頭の文字を小文字にし、後続の任意の単語の先頭を大文字にする必要があります。次に、メソッド名に関する追加の特定の命名規則を示します。

可能かつ適切であるかぎり、新しいクラス内のメソッドの名前を同様の既存のクラス(特にJava SEプラットフォームAPIのクラス)に基づいて付けると、使用しやすくなります。

フィールド名

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

フィールドには、名詞、名詞句または名詞の短縮形である名前が含まれる必要があります。

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

定数名

インタフェース内の定数の名前は、1つ以上の単語、頭字語または短縮形のシーケンスにし、すべて大文字にし、コンポーネントをアンダースコア「_」文字で区切る必要があります。また、クラスの変数finalの場合も、慣習的にこのようにする場合があります。定数名は説明的なものにし、不必要に省略しないでください。これらは慣習的に、妥当な話し言葉の一部である場合があります。

定数名の例として、クラスCharacterMIN_VALUEMAX_VALUEMIN_RADIXおよびMAX_RADIXがあります。

セットの代替値、またはそれほど頻繁ではありませんが整数値のマスキング・ビットを表す定数のグループは、場合によっては名前の接頭辞として共通の頭字語を使用して指定すると役に立ちます。

たとえば、次のようになります。

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

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

ローカル変数およびパラメータ名は短いながら意味のあるものにする必要があります。これらは多くの場合、次のような単語ではない小文字の短いシーケンスです。

1文字のローカル変数またはパラメータ名は回避する必要があります。ただし、一時またはループ変数の場合、あるいは変数に特徴のない型の値が格納されている場合は除きます。慣例的な1文字の名前は、次のとおりです。

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

第8章: クラス

8.3 フィールド宣言

クラスの変数は、フィールド宣言によって導入されます。

FieldDeclaration:
{FieldModifier} UnannType VariableDeclaratorList ;
VariableDeclaratorList:
VariableDeclarator {, VariableDeclarator}
VariableDeclarator:
VariableDeclaratorId [= VariableInitializer]
VariableDeclaratorId:
Identifier [Dims]
VariableDeclaratorId:
Identifier [Dims]
_
VariableInitializer:
Expression
ArrayInitializer
UnannType:
UnannPrimitiveType
UnannReferenceType
UnannPrimitiveType:
NumericType
boolean
UnannReferenceType:
UnannClassOrInterfaceType
UnannTypeVariable
UnannArrayType
UnannClassOrInterfaceType:
UnannClassType
UnannInterfaceType
UnannClassType:
TypeIdentifier [TypeArguments]
PackageName . {Annotation} TypeIdentifier [TypeArguments]
UnannClassOrInterfaceType . {Annotation} TypeIdentifier
[TypeArguments]
UnannInterfaceType:
UnannClassType
UnannTypeVariable:
TypeIdentifier
UnannArrayType:
UnannPrimitiveType Dims
UnannClassOrInterfaceType Dims
UnannTypeVariable Dims

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

Dims:
{Annotation} [ ] {{Annotation} [ ]}

FieldDeclaration内の各宣言子は、それぞれ1つのフィールドを宣言します。宣言子には識別子を含める必要があります。そうでない場合、コンパイル時エラーが発生します。宣言子内の識別子は、フィールドを参照する名前に使用できます。

複数の宣言子を使用して、1つのFieldDeclarationで複数のフィールドを宣言できます。複数のFieldModifierUnannTypeは、宣言内のすべての宣言子に適用されます。

FieldModifier句については、[8.3.1]を参照してください。

UnannTypeVariableDeclaratorIdに角カッコのペアがない場合は、宣言されるフィールドの型はUnannTypeによって指定され、そうでない場合は10.2で規定されています。

フィールド宣言のスコープおよびシャドウ化については、6.3および6.4.1に規定されています。

クラス宣言の本体で、同じ名前のフィールドを複数宣言すると、コンパイル時にエラーが発生します。

クラスが特定の名前のフィールドを宣言する場合、そのフィールドの宣言は、クラスのスーパークラスおよびスーパーインタフェース内の同じ名前のフィールドのすべてのアクセス可能な宣言を隠すと言うことができます。

この点において、フィールドの非表示は、メソッドの非表示(8.4.8.3)とは異なります。フィールド非表示でのstaticフィールドと非staticフィールドとの間は区別されず、メソッド非表示でのstaticメソッドと非staticメソッドとの間は区別されます。

非表示にされるフィールドには、staticの場合は修飾名(6.5.6.2)を使用するか、キーワードsuper (15.11.2)またはスーパークラス型へのキャストを含むフィールド・アクセス式を使用してアクセスできます。

この点に関しては、フィールドの非表示はメソッドの非表示と同様です。

フィールド宣言によって別のフィールドの宣言が非表示になる場合、2つのフィールドの型が同じである必要はありません。

クラスは、その直接スーパークラスおよび直接スーパーインタフェースから、スーパークラスおよびスーパーインタフェースの非privateフィールドのうち、クラス内のコードにアクセス可能(6.6)で、かつクラス内の宣言によって隠されないすべてのものを継承します。

スーパークラスとサブクラスの両方が同じクラスのメンバーである場合などは、スーパークラスのprivateフィールドにサブクラスからアクセスできます。ただし、privateフィールドがサブクラスに継承されることはありません。

クラスは、そのスーパークラスとスーパーインタフェース、またはそのスーパーインタフェースのみから、同じ名前の複数のフィールドを継承できます。そのような状況自体によってコンパイル時にエラーが発生することはありません。ただし、クラスの本体内でそのようなフィールドを単純名で参照しようとすると、参照があいまいなため、コンパイル時にエラーが発生します。

インタフェースから同じフィールドの宣言が継承されるパスが複数ある場合があります。このような状況下では、フィールドは1回のみ継承されたとみなされ、あいまいさなしに単純名で参照できます。

例8.3-1.継承フィールドの乗算

クラスは、スーパークラスおよびスーパーインタフェースから、または2つのスーパーインタフェースから、同じ名前の2つ以上のフィールドを継承できます。単純名によってあいまいな継承フィールドを参照しようとすると、コンパイル時エラーが発生します。キーワードsuper (15.11.2)を含む修飾名またはフィールド・アクセス式を使用して、このようなフィールドに明確にアクセスできます。プログラム内では次のようになります。

interface Frob  { float v = 2.0f; }
class SuperTest { int   v = 3; }
class Test extends SuperTest implements Frob {
    public static void main(String[] args) {
        new Test().printV();
    }
    void printV() { System.out.println(v); }
}

クラスTestは、vという名前のフィールドをそれぞれスーパークラスSuperTestから1つ、スーパーインタフェースFrobから1つ継承します。これ自体は許可されますが、メソッドprintVで単純名vが使用されているためコンパイル時エラーが発生します。ここでは、どのvを意味するかを判断できません。

次のバリエーションでは、フィールド・アクセス式super.vを使用して、クラスSuperTestで宣言されたvというフィールドを参照し、修飾名Frob.vを使用して、インタフェースFrobで宣言されたvというフィールドを参照します。

interface Frob  { float v = 2.0f; }
class SuperTest { int   v = 3; }
class Test extends SuperTest implements Frob {
    public static void main(String[] args) {
        new Test().printV();
    }
    void printV() {
        System.out.println((super.v + Frob.v)/2);
    }
}

次のようにコンパイルおよび出力されます。

2.5

2つの異なる継承フィールドのタイプと値が同じで、どちらもfinalである場合でも、単純名によるいずれかのフィールドへの参照はあいまいとみなされ、コンパイル時エラーになります。プログラム内では次のようになります。

interface Color        { int RED=0, GREEN=1,  BLUE=2;  }
interface TrafficLight { int RED=0, YELLOW=1, GREEN=2; }
class Test implements Color, TrafficLight {
    public static void main(String[] args) {
        System.out.println(GREEN);  // compile-time error
        System.out.println(RED);    // compile-time error
    }
}

クラスTestは異なる値を持つGREENで2つの異なる宣言を継承するため、当然ながら、GREENへの参照があいまいであるとみなされます。この例のポイントは、2つの異なる宣言が継承されるため、REDへの参照もあいまいであるとみなされることです。REDという名前の2つのフィールドのタイプが同じで、値も同じまま変わらない場合、この判断には反映されません。

例8.3-2.フィールドの再継承

同じフィールド宣言が複数のパスを経由してインタフェースから継承される場合、フィールドは1回のみ継承されるとみなされます。これは、あいまいさのない単純な名前で参照できます。たとえば、次のコードのようになります。

interface Colorable {
    int RED = 0xff0000, GREEN = 0x00ff00, BLUE = 0x0000ff;
}
interface Paintable extends Colorable {
    int MATTE = 0, GLOSSY = 1;
}
class Point { int x, y; }
class ColoredPoint extends Point implements Colorable {}
class PaintedPoint extends ColoredPoint implements Paintable {
    int p = RED;
}

フィールドREDGREENおよびBLUEは、ダイレクト・スーパークラスColoredPointとそのダイレクト・スーパーインタフェースPaintableを介して、クラスPaintedPointによって継承されます。単純名REDGREENおよびBLUEは、インタフェースColorableで宣言されたフィールドを参照するため、クラスPaintedPoint内であいまいさのない状態で使用できます。

8.4 メソッド宣言

8.4.1 仮パラメータ

メソッドまたはコンストラクタの仮パラメータがある場合、カンマ区切りパラメータ指定子のリストで指定されます。パラメータ指定子はそれぞれ、型(オプションでその前にfinal修飾子および/または1つ以上の注釈が付きます)、およびパラメータの名前を指定する識別子(オプションで角カッコが続きます)で構成されます。

メソッドまたはコンストラクタに仮パラメータもレシーバ・パラメータもない場合、メソッドまたコンストラクタの宣言に空のカッコのペアが出現します。

FormalParameterList:
FormalParameter {, FormalParameter}
FormalParameter:
{VariableModifier} UnannType VariableDeclaratorId
VariableArityParameter
VariableArityParameter:
{VariableModifier} UnannType {Annotation} ... Identifier
VariableModifier:
Annotation
final

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

VariableDeclaratorId:
Identifier [Dims]
VariableDeclaratorId:
Identifier [Dims]
_
Dims:
{Annotation} [ ] {{Annotation} [ ]}

メソッドまたはコンストラクタの仮パラメータは、型に続く省略記号が示す可変個引数パラメータである場合があります。1つのメソッドまたはコンストラクタに対して最大で1つの可変個引数パラメータが許可されています。可変個引数パラメータがパラメータ指定子のリスト内で最後の位置以外の場所に出現する場合は、コンパイル時にエラーが発生します。

VariableArityParameterの文法について、省略記号(...)はそれ自体へのトークンであることに注意してください(3.11)。それと型の間に空白を入れることは可能ですが、スタイルの面からお薦めできません。

メソッドの最後の仮パラメータが可変個引数パラメータの場合、メソッドは可変個引数メソッドになります。そうでない場合は固定個引数メソッドです。

仮パラメータ宣言およびレシーバ・パラメータに対する注釈修飾子のルールは、9.7.4および9.7.5に規定されています。

finalが仮パラメータ宣言の修飾子に複数回出現する場合、コンパイル時にエラーが発生します。

仮パラメータのスコープおよびシャドウ化については、6.3および6.4に規定されています。

ネストされたクラス、インタフェース、またはラムダ式から仮パラメータへの参照は、6.5.6.1に規定のとおり制限されています。

メソッドまたはコンストラクタの仮パラメータのすべての宣言に識別子が含まれている必要があります。そうでない場合、コンパイル時にエラーが発生します。

メソッドまたはコンストラクタで同じ名前を持つ2つの仮パラメータを宣言する場合は、コンパイル時にエラーが発生します。(つまり、その宣言は同じ識別子を指定しています。)

finalとして宣言された仮パラメータがメソッドまたはコンストラクタの本体内に割り当てられると、コンパイル時にエラーが発生します。

仮パラメータの宣言型は、それが可変個引数パラメータかどうかによって決まります。

可変個引数パラメータの宣言される型に非具象化可能な要素型(4.7)がある場合、可変個引数メソッドに@SafeVarargs (9.6.4.7)で注釈が付けられていないかぎりはそのメソッドの宣言にコンパイル時に警告がチェックされないか、警告が@SuppressWarnings (9.6.4.5)によって非表示にされます。

メソッドまたはコンストラクタが呼び出されると(15.12)、メソッドまたはコンストラクタの本体の実行前に、それぞれ宣言された型である、新しく作成されたパラメータ変数が実引数式の値によって初期化されます。FormalParameterに出現する識別子は、メソッドまたはコンストラクタの本体で、仮パラメータへの参照のための単純名として使用できます。

可変個引数メソッドの呼出しには、仮パラメータより多くの実引数式が含まれることがあります。可変個引数パラメータの前の仮パラメータに対応しない実引数式はすべて評価され、その結果が配列に格納され、メソッド呼出しに渡されます(15.12.4.2)。

インスタンス・メソッド内および内部クラスのコンストラクタ内のレシーバ・パラメータの例の一部を次に示します。

class Test {
    Test(/* ?? ?? */) {}
      // No receiver parameter is permitted in the constructor of
      // a top level class, as there is no conceivable type or name.

    void m(Test this) {}
      // OK: receiver parameter in an instance method

    static void n(Test this) {}
      // Illegal: receiver parameter in a static method

    class A {
        A(Test Test.this) {}
          // OK: the receiver parameter represents the instance
          // of Test which immediately encloses the instance
          // of A being constructed.

        void m(A this) {}
          // OK: the receiver parameter represents the instance
          // of A for which A.m() is invoked.

        class B {
            B(Test.A A.this) {}
              // OK: the receiver parameter represents the instance
              // of A which immediately encloses the instance of B
              // being constructed.

            void m(Test.A.B this) {}
              // OK: the receiver parameter represents the instance
              // of B for which B.m() is invoked.
        }
    }
}

Bのコンストラクタとインスタンス・メソッドは、レシーバ・パラメータの型が他の型と同様に、修飾子TypeNameで示されていますが、内部クラスのコンストラクタ内のレシーバ・パラメータの名前は、包含クラスの単純名を使用する必要があることを示しています。

8.10 レコード・クラス

8.10.1 レコード・コンポーネント

レコード・クラスのレコード・コンポーネントが存在する場合は、レコード宣言のヘッダーで指定されます。各レコード・コンポーネントは、型(オプションで、その前に1つ以上の注釈が付く)およびレコード・コンポーネントの名前を指定する識別子で構成されます。レコード・コンポーネントは、レコード・クラスの2つのメンバーに対応します。1つは、暗黙的に宣言されたprivateフィールド、もう1つは、明示的にまたは暗黙的に宣言されたpublicアクセッサ・メソッドです(8.10.3)。

レコード・クラスにレコード・コンポーネントがない場合、レコード宣言のヘッダーに空のカッコのペアが表示されます。

RecordHeader:
( [RecordComponentList] )
RecordComponentList:
RecordComponent {, RecordComponent}
RecordComponent:
{RecordComponentModifier} UnannType Identifier
VariableArityRecordComponent
VariableArityRecordComponent:
{RecordComponentModifier} UnannType {Annotation} ... Identifier
RecordComponentModifier:
Annotation

レコード・コンポーネントは、型に続く省略記号が示す可変個引数レコード・コンポーネントである場合があります。1つのレコード・クラスに対して最大で1つの可変個引数レコード・コンポーネントが許可されています。可変個引数レコード・コンポーネントがレコード・コンポーネントのリスト内で最後の位置以外の場所に出現する場合は、コンパイル時にエラーが発生します。

レコード・コンポーネント宣言の注釈修飾子に関するルールについては、9.7.4および9.7.5に規定されています。

レコード・コンポーネント宣言の注釈は、その注釈インタフェースがレコード・コンポーネント・コンテキスト(9.6.4.1)に適用可能な場合は、リフレクションを介して使用できます。注釈インタフェースが他のコンテキスト(8.10.38.10.4)で適用可能な場合、レコード・コンポーネント宣言の注釈は個別にレコード・クラスのメンバーおよびコンストラクタの宣言に伝播されます。

レコード宣言で名前clonefinalizegetClasshashCodenotifynotifyAlltoStringまたはwaitを持つレコード・コンポーネントを宣言する場合は、コンパイル時にエラーが発生します。

これらは、Objectの引数なしのpublicおよびprotectedメソッドの名前です。これらをレコード・コンポーネントの名前に禁止すると、様々な形で混乱を回避できます。まず、すべてのレコード・クラスは、レコード・オブジェクト全体の表現を返すhashCodeおよびtoStringの実装を提供しますが、hashCodeまたはtoStringと呼ばれるレコード・コンポーネントのアクセッサ・メソッド(8.10.3)としては使用できず、レコード・クラス外部からこのようなレコード・コンポーネントにアクセスする方法はありません。同様に、一部のレコード・クラスでは、cloneおよび(特に)finalizeの実装が提供される可能性があるため、cloneまたはfinalizeと呼ばれるレコード・コンポーネントにはアクセッサ・メソッドではアクセスできませんでした。最後に、ObjectgetClassnotifynotifyAllおよびwaitメソッドはfinalであるため、同じ名前のレコード・コンポーネントにはアクセッサ・メソッドを使用できませんでした。(アクセッサ・メソッドのシグネチャはfinalメソッドと同じであるため、オーバーライドしようとしても失敗します。)

レコード宣言で同じ名前を持つ2つのレコード・コンポーネントを宣言する場合は、コンパイル時にエラーが発生します。

レコード宣言のレコード・コンポーネントのすべての宣言に識別子が含まれている必要があります。そうでない場合、コンパイル時にエラーが発生します。

宣言されるレコード・コンポーネントの型は、それが可変個引数レコード・コンポーネントであるかどうかによって異なります。

宣言された可変個引数レコード・コンポーネントの型に型情報保持可能でない要素型(4.7)が含まれる場合に、標準コンストラクタ(8.10.4)に@SafeVarargs (9.6.4.7)の注釈が付けられていないか、または警告が@SuppressWarnings (9.6.4.5)によって抑制されていないと、コンパイル時に可変個引数レコード・コンポーネントの宣言に対して未チェック警告が発生します。

第9章 インタフェース

9.3 フィールド(定数)宣言

ConstantDeclaration:
{ConstantModifier} UnannType VariableDeclaratorList ;
ConstantModifier:
(次のうちの1つ)
Annotation public
static final

UnannTypeについては、8.3を参照してください。便宜上、ここでは4.3および8.3の次のプロダクションを示します。

VariableDeclaratorList:
VariableDeclarator {, VariableDeclarator}
VariableDeclarator:
VariableDeclaratorId [= VariableInitializer]
VariableDeclaratorId:
Identifier [Dims]
VariableDeclaratorId:
Identifier [Dims]
_
Dims:
{Annotation} [ ] {{Annotation} [ ]}
VariableInitializer:
Expression
ArrayInitializer

インタフェース・フィールド宣言の注釈修飾子に関するルールについては、9.7.4および9.7.5に規定されています。

インタフェース宣言の本体のすべてのフィールド宣言は、暗黙的にpublicstaticおよびfinalです。このようなフィールドには、これらの修飾子のいずれかまたはすべての重複指定が許可されています。

フィールド宣言について同じキーワードが修飾子として複数回出現する場合、コンパイル時にエラーが発生します。

1つのフィールド宣言に(個別)フィールド修飾子が複数ある場合、慣例ではConstantModifierのプロダクションで前述したものと一致する順序で表示されますが、必須ではありません。

UnannTypeVariableDeclaratorIdに角カッコのペアがない場合は、宣言されるフィールドの型はUnannTypeによって指定され、そうでない場合は10.2で規定されています。

ConstantDeclaration内のすべての宣言子にIdentifierを含める必要があります。そうでない場合、コンパイル時にエラーが発生します。

インタフェース・フィールド宣言のスコープおよびシャドウ化については、6.3および6.4.1に規定されています。

インタフェース・フィールドはstaticであるため、その宣言では静的コンテキスト(8.1.3)が導入され、現在のオブジェクトを参照する構成の使用が制限されます。特に、キーワードthisおよびsuperは、静的コンテキスト(15.8.315.11.2)では禁止されています。これは、字句として囲んでいる宣言(6.5.5.16.5.6.115.12.3)のインスタンス変数、インスタンス・メソッドおよび型パラメータに対する参照が修飾されていないためです。

インタフェース宣言の本体で、同じ名前のフィールドを複数宣言すると、コンパイル時にエラーが発生します。

インタフェースが特定の名前のフィールドを宣言する場合、そのフィールドの宣言は、インタフェースのスーパーインタフェース内で同じ名前のフィールドのすべてのアクセス可能な宣言を隠すと言うことができます。

インタフェースは、同じ名前の複数のフィールドを継承できます。そのような状況自体によってコンパイル時にエラーが発生することはありません。ただし、インタフェース宣言の本体内でそのようなフィールドを単純名で参照しようとすると、参照があいまいなため、コンパイル時にエラーが発生します。

インタフェースから同じフィールドの宣言が継承されるパスが複数ある場合があります。このような状況下では、フィールドは1回のみ継承されたとみなされ、あいまいさなしに単純名で参照できます。

例9.3-1.あいまいな継承フィールド

たとえば、2つの直接スーパーインタフェースがその名前でフィールドを宣言するため、同じ名前の2つのフィールドが1つのインタフェースに継承されると、単一のあいまいなメンバー結果が生じます。このあいまいなメンバーを使用すると、コンパイル時にエラーが発生します。プログラム内では次のようになります。

interface BaseColors {
    int RED = 1, GREEN = 2, BLUE = 4;
}
interface RainbowColors extends BaseColors {
    int YELLOW = 3, ORANGE = 5, INDIGO = 6, VIOLET = 7;
}
interface PrintColors extends BaseColors {
    int YELLOW = 8, CYAN = 16, MAGENTA = 32;
}
interface LotsOfColors extends RainbowColors, PrintColors {
    int FUCHSIA = 17, VERMILION = 43, CHARTREUSE = RED+90;
}

インタフェースLotsOfColorsは、YELLOWという名前の2つのフィールドを継承します。これは、単純名によるフィールドYELLOWへの参照がインタフェースに含まれていないかぎり有効です。(このような参照は、フィールドの変数イニシャライザ内で発生する可能性があります。)

インタフェースPrintColorsが値8ではなく値3YELLOWに指定する場合でも、インタフェースLotsOfColors内でのフィールドYELLOWへの参照はあいまいとみなされます。

例9.3-2.継承フィールドの乗算

たとえば、このインタフェースとこのインタフェースの直接スーパーインタフェースの両方がフィールドを宣言するインタフェースを拡張するなど、単一のフィールドが同じインタフェースから複数回継承される場合、単一のメンバー結果だけが生じます。このような状況自体によってコンパイル時にエラーが発生することはありません。

前述の例では、フィールドREDGREENおよびBLUEは、インタフェースRainbowColorsおよびインタフェースPrintColorsを介して、インタフェースLotsOfColorsによって複数の方法で継承されますが、インタフェースLotsOfColorsのフィールドREDへの参照は、フィールドREDの実際の宣言が1つだけであるため、あいまいとはみなされません。

第14章: ブロックと文およびパターン

次の変更は、JEP 440 (レコード・パターン)およびJEP 441 (switchのパターン・マッチング)によるJLS変更が適用されていることを前提としています(JLS:JEP440+441)。

14.4 ローカル変数宣言

ローカル変数宣言では、1つ以上のローカル変数を宣言し、オプションで初期化します(4.12.3)。

LocalVariableDeclaration:
{VariableModifier} LocalVariableType VariableDeclaratorList
LocalVariableType:
UnannType
var

UnannTypeについては、8.3を参照してください。便宜上、4.38.3および8.4.1からの次のプロダクションをここに示します。

VariableModifier:
Annotation
final
VariableDeclaratorList:
VariableDeclarator {, VariableDeclarator}
VariableDeclarator:
VariableDeclaratorId [= VariableInitializer]
VariableDeclaratorId:
Identifier [Dims]
VariableDeclaratorId:
Identifier [Dims]
_
Dims:
{Annotation} [ ] {{Annotation} [ ]}
VariableInitializer:
Expression
ArrayInitializer

ローカル変数宣言は次の場所で使用できます。

ローカル変数宣言の注釈修飾子に関するルールについては、9.7.4および9.7.5に規定されています。

キーワードfinalがローカル変数宣言の修飾子として表示される場合、ローカル変数はfinal変数です(4.12.4)。

finalがローカル変数宣言の修飾子として複数回出現する場合は、コンパイル時にエラーが発生します。

Identifierを含んでおらず、イニシャライザを持たないローカル変数宣言が次の場所で使用されている場合、コンパイル時にエラーが発生します。

LocalVariableTypevarで、次のいずれかに当てはまる場合は、コンパイル時にエラーが発生します。

例14.4-1.varで宣言されるローカル変数

次のコードは、varの使用を制限するこれらのルールを示しています。

var a = 1;            // Legal
var b = 2, c = 3.0;   // Illegal: multiple declarators
var d[] = new int[4]; // Illegal: extra bracket pairs
var e;                // Illegal: no initializer
var f = { 6 };        // Illegal: array initializer
var g = (g = 7);      // Illegal: self reference in initializer

これらの制限は、varで表されている型の混乱を避けるために役立ちます。

14.11 switch

switch文は、式の値に応じて、いくつかの文または式の1つに制御を転送します。

SwitchStatement:
switch ( Expression ) SwitchBlock

Expressionセレクタ式と呼ばれます。セレクタ式のタイプはcharbyteshortintまたは参照タイプである必要があり、そうでない場合、コンパイル時にエラーが発生します。

14.11.1 Switchブロック

switch文およびswitch式(15.28)の両方の本体は、switchブロックと呼ばれます。このサブセクションでは、switch文またはswitch式のどちらに出現するかに関係なく、すべてのswitchブロックに適用される一般的なルールを示します。他のサブセクションでは、switch文のswitchブロック(14.11.2)またはswitch式のswitchブロック(15.28.1)のいずれかに適用される追加ルールを示します。

SwitchBlock:
{ SwitchRule {SwitchRule} }
{ {SwitchBlockStatementGroup} {SwitchLabel :} }
SwitchRule:
SwitchLabel -> Expression ;
SwitchLabel -> Block
SwitchLabel -> ThrowStatement
SwitchBlockStatementGroup:
SwitchLabel : { SwitchLabel :} BlockStatements
SwitchLabel:
case CaseConstant {, CaseConstant}
case null [, default]
case CasePattern [ Guard ]
default
SwitchLabel:
case CaseConstant {, CaseConstant}
case null [, default]
case CasePattern{, CasePattern } [ Guard ]
default
CaseConstant:
ConditionalExpression
CasePattern:
Pattern
Guard:
when Expression

switchブロックは次のいずれかで構成できます。

すべてのswitchルールおよびswitchラベルの付いた文グループは、switchラベル (caseラベルまたはdefaultラベルのいずれか)で始まります。複数のswitchラベルが、1つのswitchラベルが付いた文グループに許可されます。

caseラベルは、case定数のリストまたは単一の1つ以上のcaseパターンのいずれかで構成されます。

caseパターンを持つcaseラベルには、ガードと呼ばれるオプションのwhen式を含めることができます。これは、パターンに一致する値に対する追加のテストを表します。caseラベルは、(i)その要素にガードがない場合、または(ii)値がtrueの定数式(15.29)であるガードがその要素にある場合に、ガードなしと表現されます。それ以外の場合はガード付きです。

switchラベルおよびそのcase定数およびcaseパターンは、switchブロックに関連付けられていると言い表されます。

特定のswitchブロックの場合、次の両方が当てはまる必要があり、そうでない場合、コンパイル時にエラーが発生します:

caseラベルに関連付けられているガードは、boolean型またはBoolean型である必要があります。ガードによって宣言されていない変数は、finalまたは事実上final (4.12.4)であり、(15.26)、増分([15.14.2])、または減分([15.14.3])に割り当てることはできません。そうでない場合、コンパイル時エラーが発生します。ガードがfalseの値を持つ定数式(15.29)の場合は、コンパイル時エラーになります。

次のすべてに該当する場合、switch文またはswitch式のswitchブロックは、セレクタ式T型とのswitch互換性があります:

switchブロックは、booleanlongfloatおよびdouble型と連携するように設計されていません。switch文のセレクタ式またはswitch式には、これらの型のどれも指定できません。

switch文またはswitch式のswitchブロックは、セレクタ式の型とのswitch互換性が必要であり、そうでない場合はコンパイル時にエラーが発生します。

switchブロック内のswitchラベルは、それが適用されるすべての値に対して、前述のいずれかのswitchラベルも適用される場合、それより優先されないものとみなされます。switchブロック内のどのswitchラベルも優先されない場合、コンパイル時にエラーになります。switchラベルが優先されるかどうかを判断するするためのルールは次のとおりです。

switchブロック内にcaseパターンp1,...,pn (n > 1)があり、パターンの1つpi (1 ≤ i < n)が他のパターンpj (i < j ≤ n)より優先されるcaseパターンがある場合、コンパイル時にエラーが発生します。

switchラベルが付いた文グループで構成されるswitchブロックで、1つ以上のパターン変数を宣言するcaseパターンで文がラベル付けされていて、次のいずれかの場合、コンパイル時にエラーが発生します:

最初の条件により、文グループがパターン変数を初期化せずに別の文グループにフォール・スルーすることがなくなります。たとえば、前述の文グループから到達可能なcase Integer iというラベルが付けられた文では、パターン変数iは初期化されていません:

Object o = "Hello";
switch (o) {
    case String s:
        System.out.println("String: " + s );  // No break!
    case Integer i:
        System.out.println(i + 1);            // Error! Can be reached
                                              // without matching the
                                              // pattern `Integer i`
    default:
}

switchラベル文グループで構成されるswitchブロックでは、複数のラベルを文グループに適用できます。2番目の条件は、別のラベルのパターン変数を初期化せずに、あるラベルに基づいて文グループが実行されないようにします。たとえば、次のようになります。

Object o = "Hello World";
switch (o) {
    case String s:
    case Integer i:
        System.out.println(i + 1);  // Error! Can be reached
                                    // without matching the
                                    // pattern `Integer i`
    default:
}

Object obj = null;
switch (obj) {
    case null:
    case String s:
        System.out.println(s);      // Error! Can be reached
                                    // without matching the
                                    // pattern `String s`
    default:
}

これらの条件は両方とも、caseパターンがパターン変数を宣言している場合にのみ適用されます。一方、次の例は問題ありません:

record R() {}
record S() {}

Object o = "Hello World";
switch (o) {
    case String s:
        System.out.println(s);        // No break!
    case R():
        System.out.println("It's either an R or a string");
        break;
    default:
}

Object ob = new R();
switch (ob) {
    case R():
    case S():                         // Multiple case labels!
        System.out.println("Either R or an S");
        break;
    default:
}

Object obj = null;
switch (obj) {
    case null:
    case R():                         // Multiple case labels!
        System.out.println("Either null or an R");
        break;
    default:
}
14.11.1.2 実行時に適用されるswitchラベルの決定

switch文の実行(14.11.3)およびswitch式の評価(15.28.2)の両方で、switchブロックに関連付けられたswitchラベルがセレクタ式の値に適用されるかどうかを判断する必要があります。これは次のように処理されます。

  1. 値がnull参照の場合、nullcase定数を含むcaseラベルが適用されます。

  2. 値がnull参照ではない場合、値に適用されるswitchブロックの最初の(ある場合) caseラベルを次のように決定します:

    • null以外のcase定数cを持つcaseラベルは、最初に値がボックス化解除変換(5.1.8)の対象になり、定数cがボックス化解除した値と等しくなる場合、CharacterByteShortまたはInteger型の値に適用されます。

      ボックス化解除変換は、ボックス化解除される値がNULL参照にならないことが保証されるため、正常に完了する必要があります。

      等価性は、==演算子で定義されます(15.21)。

    • null以外のcase定数cを持つcaseラベルは、定数cが値と等しい場合、CharacterByteShortまたはInteger型ではない値に適用されます。

      等価性は==演算子によって定義されますが、値がStringの場合はStringクラスのequalsメソッドによって定義されます。

    • caseパターンpを持つcaseラベルが値に適用されることを確認するには、最初に、値がパターンp (14.30.2)に一致するかどうかをチェックします。

      caseパターンp1,...,pn (n ≥ 1)を持つcaseラベルが値に適用されることを確認するには、適用される最初のcaseパターンpi (1≤i≤n) (存在する場合)を決定します。

      適用するcaseパターンを決定するプロセスが突然完了した場合は、適用されるswitchラベルを判断するプロセスについても同じ理由で突然完了します。

      caseパターンpが値に適用されることを確認するには、最初に、値がパターンp (14.30.2)に一致するかどうかをチェックします。

      パターン・マッチングが突然完了した場合は、適用されるswitchラベルcaseパターンを判断するプロセスについても同じ理由で突然完了します。

      パターン・マッチングが成功し、caseラベルパターンがガードされていない場合、このcaseラベルパターンが適用されます。

      パターン・マッチングが成功し、caseラベルパターンがガードされている場合、ガードが評価されます。その結果がBoolean型の場合は、ボックス化解除変換(5.1.8)の対象になります。

      ガードまたは後続のボックス化解除変換(ある場合)の評価がなんらかの理由で突然完了した場合、適用するswitchラベルパターンを判断するプロセスは同じ理由で突然完了します。

      それ以外の場合は、その結果の値がtrueのときに、caseラベルパターンが適用されます。

    • case null, defaultラベルはすべての値に適用されます

  3. 値がnull参照ではなく、ステップ2のルールに従ってcaseラベルが適用されない場合、defaultラベルがswitchブロックに関連付けられている場合は、そのラベルが適用されます。

単一のcaseラベルには、複数のcase定数を含めることができます。ラベルは、その定数のいずれかがセレクタ式の値と等しい場合、セレクタ式の値に適用されます。たとえば、次のコードで、enum変数dayが表示されているenum定数のいずれかである場合、caseラベルは一致します:

switch (day) {
    ...
    case SATURDAY, SUNDAY :
        System.out.println("It's the weekend!");
        break;
    ...
}

caseパターンのあるcaseラベルが適用される場合、これは、パターンに対する値のパターン・マッチング・プロセスが成功したことによるものです(14.30.2)。値がパターンと正常に一致すると、パターン・マッチングのプロセスは、パターンによって宣言されたパターン変数を初期化します。

過去の理由から、defaultラベルは、defaultラベルの後に一部のラベルが出現する場合でも、すべてのcaseラベルが一致しなかった場合にのみ考慮されます。ただし、後続のラベルでは、null以外のcase定数(14.11.1)のみを使用でき、スタイルによっては、プログラマはdefaultラベルを最後に配置することをお薦めします。

CおよびC++で、switch文の本体は文(単数および複数)にすることができ、caseラベルをその文で直接含む必要はありません。単純なループを考えてみましょう。

for (i = 0; i < n; ++i) foo();

ここでnは正であると知られています。ダフスデバイスとして知られているトリックは、ループをアンロールするためにCまたはC++で使用できますが、これはJavaプログラミング言語では有効なコードではありません。

int q = (n+7)/8;
switch (n%8) {
    case 0: do { foo();    // Great C hack, Tom,
    case 7:      foo();    // but it's not valid here.
    case 6:      foo();
    case 5:      foo();
    case 4:      foo();
    case 3:      foo();
    case 2:      foo();
    case 1:      foo();
            } while (--q > 0);
}

幸いにも、このトリックは広く認識または使用されてはいないようです。さらに、今日ではあまり必要ではなくなりました。この種のコード変換は、適切に最新式の最適化コンパイラの領域にあります。

14.14 for

14.14.2 拡張されたfor

拡張されたfor文の形式は次のようになります。

EnhancedForStatement:
for ( LocalVariableDeclaration : Expression )
Statement
EnhancedForStatementNoShortIf:
for ( LocalVariableDeclaration : Expression )
StatementNoShortIf

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

LocalVariableDeclaration:
{VariableModifier} LocalVariableType VariableDeclaratorList
VariableModifier:
Annotation
final
LocalVariableType:
UnannType
var
VariableDeclaratorList:
VariableDeclarator {, VariableDeclarator}
VariableDeclarator:
VariableDeclaratorId [= VariableInitializer]
VariableDeclaratorId:
Identifier [Dims]
VariableDeclaratorId:
Identifier [Dims]
_
Dims:
{Annotation} [ ] {{Annotation} [ ]}

の型は、配列型(10.1)またはRAW型Iterableのサブタイプである必要があります。そうしないと、コンパイル時にエラーが発生します。

拡張されたfor文のヘッダーは、VariableDeclaratorIdが指定した識別子であるローカル変数を宣言するか、あるいは名前のないローカル変数(6.1)を宣言します。拡張されたfor文が実行されると、ローカル変数がそれぞれのループの反復で、式によって生成された連続するIterableの要素または配列に初期化されます。

拡張されたfor文のヘッダーで宣言されたローカル変数のルールは、14.4で規定され、LocalVariableTypevarの場合に適用されるそのセクション内のルールが無視されます。さらに、次のすべてが当てはまる必要があり、そうでない場合、コンパイル時にエラーが発生します。

拡張されたfor文のヘッダーで宣言されているローカル変数のスコープとシャドウ化は、6.3および6.4で規定されています。

ネストされたクラスまたはインタフェースまたはラムダ式からのローカル変数への参照は、6.5.6.1で規定されているように制限されます。

拡張されたfor文のヘッダーで宣言されたローカル変数の型Tは、次のように決定されます:

拡張されたforヘッダーでローカル変数識別子を宣言することの正確な意味は、次のように基本for文への変換によって規定されます。

たとえば、次のコードがあるとします。

List<? extends Integer> l = ...
for (float i : l) ...

次のように変換されます。

for (Iterator<Integer> #i = l.iterator(); #i.hasNext(); ) {
    float #i0 = (Integer)#i.next();
    ...

例14.14-1.拡張されたforおよび配列

整数配列の合計を計算する次のプログラムは、拡張されたforが配列に対してどのように機能するかを示しています。

int sum(int[] a) {
    int sum = 0;
    for (int i : a) sum += i;
    return sum;
}

例14.14-2.拡張されたforおよびボックス化解除変換

次のプログラムは、拡張されたfor文と自動ボックス化解除を組み合せて、ヒストグラムを度数分布表に変換します。

Map<String, Integer> histogram = ...;
double total = 0;
for (int i : histogram.values())
    total += i;
for (Map.Entry<String, Integer> e : histogram.entrySet())
    System.out.println(e.getKey() + " " + e.getValue() / total);
}

拡張されたfor文のヘッダーで名前のないローカル変数を宣言することの厳密な意味は、前述のように基本のfor文に変換し、名前のないローカル変数を識別子のかわりに使用することで規定できます。

14.20 try

14.20.3 try-with-resources

try-with-resources文は、tryブロックの実行後に初期化された順序とは逆に、変数(resourcesと呼ばれます)でパラメータ化され、tryブロックの実行前に初期化され、自動的に終了します。catch句とfinally句は通常、リソースが自動的に終了すると不要になります。

TryWithResourcesStatement:
try ResourceSpecification Block [Catches] [Finally]
ResourceSpecification:
( ResourceList [;] )
ResourceList:
Resource {; Resource}
リソース:
LocalVariableDeclaration
VariableAccess
VariableAccess:
ExpressionName
FieldAccess

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

LocalVariableDeclaration:
{VariableModifier} LocalVariableType VariableDeclaratorList
VariableModifier:
Annotation
final
LocalVariableType:
UnannType
var
VariableDeclaratorList:
VariableDeclarator {, VariableDeclarator}
VariableDeclarator:
VariableDeclaratorId [= VariableInitializer]
VariableDeclaratorId:
Identifier [Dims]
VariableDeclaratorId:
Identifier [Dims]
_
Dims:
{Annotation} [ ] {{Annotation} [ ]}
VariableInitializer:
Expression
ArrayInitializer

UnannTypeについては、8.3を参照してください。

リソースの指定は、イニシャライザ式でローカル変数を宣言、または既存の変数を参照することによって、try-with-resources文のresourcesを示します。既存の変数は式名(6.5.6)またはフィールド・アクセス式(15.11)によって参照されます。

リソース指定で宣言されたローカル変数のルールは、14.4で規定されています。さらに、次のすべてが当てはまる必要があり、そうでない場合、コンパイル時にエラーが発生します。

リソース指定で宣言されたローカル変数のスコープとシャドウ化は、6.3および6.4で規定されています。

ネストされたクラスまたはインタフェースまたはラムダ式からのローカル変数への参照は、6.5.6.1で規定されているように制限されます。

リソース指定で宣言されたローカル変数の型は、14.4.1で規定されています。

リソース指定で宣言されたローカル変数の型、またはリソース指定で参照された既存の変数の型はAutoCloseableのサブタイプである必要があり、そうでない場合はコンパイル時にエラーが発生します。

リソースの指定で、同じ名前のローカル変数を複数宣言すると、コンパイル時にエラーが発生します。

リソース指定には、複数の名前のないローカル変数(6.1)の宣言が含まれている場合があります。

リソースは、次の点でfinalです。

リソースは左から右に初期化されます。リソースの初期化に失敗(つまり、イニシャライザ式が例外をスロー)する場合、それまでtry-with-resources文で初期化されたリソースはすべてクローズされます。すべてのリソースの初期化が完了すると、tryブロックが正常に実行され、その後、try-with-resources文のnullでないリソースがクローズされます。

リソースは、初期化された位置から逆方向にクローズします。リソースは、nullではない値に初期化された場合のみクローズします。1つのリソースのクローズからの例外が他のリソースのクローズを妨げることはありません。イニシャライザ、tryブロック、またはリソースのクローズによってすでに例外がスローされている場合、例外は非表示になります。

リソース指定が複数のリソースを示すtry-with-resources文は、複数のtry-with-resources文であり、そのそれぞれが単一のリソースを示すリソース指定を持つものとして扱われます。nリソース(n > 1)を持つtry-with-resources文が変換されると、その結果はn-1リソースを持つtry-with-resources文になります。nが変換されると、ntry-catch-finally文にネストされ、全体の変換が完了します。

14.20.3.1基本のtry-with-resources

catch句またはfinally句のないtry-with-resources文は、基本のtry-with-resources文と呼ばれます。

基本のtry-with-resources文の形式が次の場合:

try (VariableAccess ...)
    Block

リソースは、次の変換によって最初にローカル変数宣言に変換されます。

try (T #r = VariableAccess ...) {
    Block
}

TVariableAccessが示す変数の型、#rは自動生成される識別子であり、try-with-resources文が出現するポイントでスコープ内にある、他のいかなる識別子(自動生成またはその他)とも異なります。try-with-resources文は、このセクションの後半部分に従って変換されます。

次の形式の基本のtry-with-resources文の意味:

try ({VariableModifier} R Identifier = Expression ...)
    Block

これはローカル変数宣言およびtry-catch-finally文への次のような変換によって指定されます。

{
    final {VariableModifierNoFinal} R Identifier = Expression;
    Throwable #primaryExc = null;

    try ResourceSpecification_tail
        Block
    catch (Throwable #t) {
        #primaryExc = #t;
        throw #t;
    } finally {
        if (Identifier != null) {
            if (#primaryExc != null) {
                try {
                    Identifier.close();
                } catch (Throwable #suppressedExc) {
                    #primaryExc.addSuppressed(#suppressedExc);
                }
            } else {
                Identifier.close();
            }
        }
    }
}

{VariableModifierNoFinal}final (存在する場合)なしで{VariableModifier}として定義されます。

#t#primaryExc、および#suppressedExcは自動生成される識別子となり、try-with-resources文が出現するポイントでスコープ内にある、他のいかなる識別子(自動生成またはその他)とも異なります。

または、次の形式の基本のtry-with-resources文の意味:

try ({VariableModifier} R _ = Expression ...)
    Block

これはローカル変数宣言およびtry-catch-finally文への次のような変換によって指定されます。

{
    final {VariableModifierNoFinal} R #i = Expression;
    Throwable #primaryExc = null;

    try ResourceSpecification_tail
        Block
    catch (Throwable #t) {
        #primaryExc = #t;
        throw #t;
    } finally {
        if (#i != null) {
            if (#primaryExc != null) {
                try {
                    #i.close();
                } catch (Throwable #suppressedExc) {
                    #primaryExc.addSuppressed(#suppressedExc);
                }
            } else {
                #i.close();
            }
        }
    }
}

{VariableModifierNoFinal}final (存在する場合)なしで{VariableModifier}として定義されます。

#t#primaryExc#suppressedExc、および#iは自動生成される識別子となり、try-with-resources文が出現するポイントでスコープ内にある、他のいかなる識別子(自動生成またはその他)とも異なります。

リソース指定が1つのリソースを示している場合、ResourceSpecification_tailは空です(そしてtry-catch-finally文はそれ自体がtry-with-resources文ではありません)。

リソース指定がn > 1リソースを示している場合、ResourceSpecification_tailは、リソース指定に示されている2番目、3番目...、n番目のリソースと同じ順序で構成されます(そしてtry-catch-finally文はそれ自体がtry-with-resources文です)。

基本のtry-with-resources文の到達可能性と明確な代入ルールは、前述の変換変換によって暗黙的に指定されます。

単一のリソースを管理する基本のtry-with-resources文では、次のようになります。

複数のリソースを管理する基本のtry-with-resources文では、次のようになります。

14.20.3.2 拡張try-with-resources

少なくとも1つのcatch句またはfinally句(あるいはその両方)を含むtry-with-resources文は、拡張try-with-resources文と呼ばれます。

拡張try-with-resources文の意味:


try ResourceSpecification
    Block
[Catches]
[Finally]

これはtry-catch文、try-finally文、またはtry-catch-finally文内にネストされた基本のtry-with-resources文への次の変換によって指定されます。


try {
    try ResourceSpecification
        Block
}
[Catches]
[Finally]

変換すると、リソース指定がtry文内に配置されます。これにより、拡張try-with-resources文のcatch句で、リソースの自動初期化またはクローズによる例外を捕捉できます。

さらに、finallyキーワードの意図に従って、finallyブロックが実行されるまでにすべてのリソースがクローズ(またはクローズが試行)されます。

14.30 パターン

14.30.1 パターンの種類

次の変更は、JEP 440 (レコード・パターン)およびJEP 441 (switchのパターン・マッチング)によるJLS変更が適用されていることを前提としています(JLS:JEP440+441)。

型パターンは、値がパターンに出現する型のインスタンスであるかどうかをテストするために使用されます。レコード・パターンは、値がレコード・クラス型のインスタンスであるかどうかをテストし、そうである場合は、レコード・コンポーネント値に対してパターン・マッチングを再帰的に実行するために使用されます。

Pattern:
TypePattern
RecordPattern
TypePattern:
LocalVariableDeclaration
RecordPattern:
ReferenceType ( [ PatternList ComponentPatternList ] )
PatternList :
Pattern { , Pattern }
ComponentPatternList:
ComponentPattern { , ComponentPattern }
ComponentPattern:
Pattern
UnnamedPattern
UnnamedPattern:
_

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

LocalVariableDeclaration:
{VariableModifier} LocalVariableType VariableDeclaratorList
VariableModifier:
Annotation
final
LocalVariableType:
UnannType
var
VariableDeclaratorList:
VariableDeclarator {, VariableDeclarator}
VariableDeclarator:
VariableDeclaratorId [= VariableInitializer]
VariableDeclaratorId:
Identifier [Dims]
VariableDeclaratorId:
Identifier [Dims]
_
Dims:
{Annotation} [ ] {{Annotation} [ ]}

UnannTypeについては、8.3を参照してください。

レコード・パターンのネストされたパターン・リストに要素として出現しないパターンは、トップレベル型パターンと呼ばれ、それ以外はネストしたパターンと呼ばれます。

型パターンは1つのパターン変数を宣言します。このパターン変数は、(_で示される)名前のないパターン変数にできます。それ以外の場合は、ローカル変数宣言の識別子でパターン変数の名前が指定されます。

型パターンで宣言されたパターン変数のルールについては、14.4に規定されています。さらに、次のすべてが当てはまる必要があり、そうでない場合、コンパイル時にエラーが発生します。

トップレベル型パターンで宣言されたパターン変数の型は、LocalVariableTypeによって示される参照型です。

ネストされた型パターンで宣言されたパターン変数の型は、次のように決定されます。

型パターンは、型がRのレコード・パターンのパターン・リストに要素として表示され、Rの対応するレコード・コンポーネントに型Uがあり、型パターンが型U (14.30.3)に対して無条件である場合、null一致と呼ばれます。

型パターンのこのコンパイル時プロパティは、パターン・マッチング(14.30.2)のプロセスで使用されるため、実行時に使用する型パターンに関連付けられています。

レコード・パターンは、ReferenceTypeとネストされたコンポーネント・パターン・リストで構成されます。ReferenceTypeがレコード・クラス・タイプ(8.10)でない場合、コンパイル時エラーが発生します。

ReferenceTypeがRAW型である場合、[18.5.5]で説明されているように、レコード・パターンの型が推論されます。レコード・パターンに対して型を推論できない場合、コンパイル時にエラーが発生します。

それ以外の場合、レコード・パターンの型はReferenceTypeです。

レコード・パターンのネストされたコンポーネント・パターン・リストの長さは、ReferenceTypeで指定されたレコード・クラスの宣言内のレコード・コンポーネント・リストと同じ長さである必要があります。そうでない場合は、コンパイル時にエラーが発生します。

現時点では、可変個引数レコード・パターンはサポートされていません。これは、Javaプログラミング言語の将来のバージョンでサポートされる可能性があります。

レコード・パターンでは、ネストされたコンポーネント・パターン・リスト内のパターンで宣言されているパターン変数(ある場合)を宣言します。

名前のないパターンは、型がRのレコード・パターンのコンポーネント・パターン・リストに要素としてのみ表示できる特殊なパターンです。TRの対応するコンポーネント・フィールドの型にします。名前のないパターンはパターン変数を宣言せず、その型はTが示すすべての合成型変数に関してTの上方投影として定義されます。名前のないパターンは常にnull一致です。

14.30.2 パターン・マッチング

パターン・マッチングは、実行時にパターンに対して値をテストするプロセスです。パターン・マッチングは、文実行(14.1)および式評価(15.1)とは異なります。値がパターンに正常に一致する場合、パターン・マッチング・プロセスが、パターンによって宣言されたすべてのパターン変数(ある場合)を初期化します。

パターン・マッチングのプロセスには、式の評価または文の実行が関与する場合があります。したがって、式の評価または文の実行が突然完了する場合は、パターン・マッチングが突然完了すると表現されます。突然の完了には常に理由が関連付けられており、それは常に、指定された値を持つthrowです。パターン・マッチングは、突然完了しない場合、正常に完了すると言い表されます。

値がパターンと一致するかどうかを決定するルールおよびパターン変数を初期化するルールは、次のとおりです。