モジュール・インポート宣言(プレビュー)

Java®言語仕様の変更点・バージョン23.0.1+11-39

このドキュメントでは、Java SE 23のプレビュー機能であるモジュール・インポート宣言をサポートするためのJava言語仕様に対する変更点について説明します。この機能の概要は、JEP 476を参照してください。

JEP 477で提案されたプレビュー機能の暗黙的に宣言されたクラスおよびインスタンスのmainメソッドは、この機能に依存します。

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

変更ログ:

2024-06-04: 欠落している更新をセクション3.9に追加しています

2024-05-07: JEP 477リンクで更新しました。

2024-04: 初稿

第3章: 字句構造

3.9 キーワード

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

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

キーワード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)で終端として認識される場合。
    • moduleについては、SingleModuleImportDeclaration (7.5.5)またはModuleDeclaration (7.7)で端末として認識される場合。

    • 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 宣言

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

この項の残りの部分に変更はありません。

6.3 宣言のスコープ

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

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

観察可能な最上位パッケージ(7.4.3)の宣言のスコープは、パッケージが一意として表示されるモジュールに関連付けられたすべての観察可能なコンパイル・ユニットです(7.4.3)。

観察可能でないパッケージの宣言はスコープ内ではありません。

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

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

単一型インポート宣言(7.5.1)またはオンデマンド型インポート宣言(7.5.2)または単一モジュール・インポート宣言(7.5.5)によってインポートされるクラスまたはインタフェースのスコープは、モジュール宣言(7.7)、およびimport宣言が示されるコンパイル・ユニットのすべてのクラス宣言およびインタフェース宣言(8.19.1)、ならびにコンパイル・ユニットのモジュール宣言またはパッケージ宣言の注釈です。

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

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

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

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

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

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

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

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

ブロック(14.2)で直接囲まれたローカル・クラスまたはインタフェース宣言のスコープは、直接囲んでいるブロックの残りの部分であり、そのローカル・クラスまたはインタフェース宣言自体を含みます。

switchブロック文グループ(14.11)で直接囲まれたローカル・クラスまたはインタフェース宣言のスコープは、直接囲んでいるswitchブロック文グループの残りの部分であり、そのローカル・クラスまたはインタフェース宣言自体を含みます。

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

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

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

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

try-with-resources文の翻訳は、前述のルールを示します。

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

この項の残りの部分に変更はありません。

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

6.4.1 シャドウ化

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

シャドウ化は、非表示(8.38.4.8.28.59.39.5)とは異なります。非表示は、サブクラス内で宣言されているために継承されておらず、そうでなければ継承されていたメンバーにのみ適用されます。また、シャドウ化は不明瞭化(6.4.2)とも異なります。

nという名前の型の宣言dは、dのスコープ全体にわたって、dが発生するポイントで、スコープ内にあるnという名前の他の型の宣言をシャドウ化します。

nという名前のフィールドまたは仮パラメータの宣言dは、dのスコープ全体にわたって、dが発生するポイントでスコープ内にあるnという名前の他の変数の宣言をシャドウ化します。

nという名前のローカル変数または例外パラメータの宣言dは、dのスコープ全体にわたって、(a) dが発生するポイントでスコープ内にあるnという名前の他のフィールドの宣言、および(b) dが発生するポイントでスコープ内にあるが、dが宣言された最も内側のクラス内で宣言されていないnという名前の他の変数の宣言をシャドウ化します。

nという名前のメソッドの宣言dは、dのスコープ全体にわたって、dが発生するポイントでそれを囲むスコープ内にあるnという名前の他のメソッドの宣言をシャドウ化します。

パッケージ宣言によって他の宣言がシャドウ化されることはありません。

オンデマンド型インポート宣言によって他の宣言がシャドウ化されることはありません。

オンデマンド静的インポート宣言によって他の宣言がシャドウ化されることはありません。

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

nという名前の型をインポートするパッケージpのコンパイル・ユニットc内の単一型インポート宣言dは、c全体にわたって、次の宣言をシャドウ化します。

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

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

nという名前の型をインポートするパッケージpのコンパイル・ユニットc内の単一静的インポート宣言dは、c全体にわたって、次の宣言をシャドウ化します。

この項の残りの部分に変更はありません。

6.5 名前の意味の確認

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

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

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

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

前述の17個のコンテキスト内のReferenceTypeの識別子からのTypeNameの抽出は、要素型や型引数などのReferenceTypeのサブ用語すべてに繰り返し適用することを意図しています。

たとえば、フィールド宣言に型p.q.Foo[]が使用されるとします。配列型のカッコは無視され、用語p.q.Foo識別子の点線シーケンスとして配列型内のカッコの左側に抽出され、TypeNameとして分類されます。後のステップで、pqおよびFooのどれが型名またはパッケージ名であるかが確認されます。

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

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

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

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

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

構文的分類の結果、特定の種類のエンティティを式の特定の部分に制限できるようになります。

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

7.5 インポート宣言

インポート宣言を使用すると、名前付きクラス、インタフェースまたはstaticメンバーを、単一の識別子で構成される単純名(6.2)で参照できます。

適切なインポート宣言がなしでは、別のパッケージで宣言されているクラスやインタフェースへの参照、または別のクラスやインタフェースのstaticメンバーへの参照には、通常、完全修飾名(6.7)の使用が必要になります。

ImportDeclaration:
SingleTypeImportDeclaration
TypeImportOnDemandDeclaration
SingleStaticImportDeclaration
StaticImportOnDemandDeclaration
SingleModuleImportDeclaration

これらの宣言によってインポートされるクラス、インタフェースまたはメンバーのスコープおよびシャドウ化については、6.3および6.4で規定されています。

import宣言によって、実際にそのimport宣言を含むコンパイル・ユニット内でのみ、クラス、インタフェースまたはメンバーを単純名で使用できるようになります。import宣言によって導入されるクラス、インタフェースまたはメンバーのスコープには、同じパッケージ内の他のコンパイル・ユニット、現在のコンパイル・ユニット内の他のimport宣言、または現在のコンパイル・ユニット内のpackage宣言(package宣言の注釈は除く)は本質的に含まれません。

7.5.5 単一モジュール・インポート宣言

単一モジュール・インポート宣言では、名前付きモジュールでエクスポートされたパッケージのすべてのpublicの最上位クラスとインタフェースを必要に応じてインポートできます。

SingleModuleImportDeclaration:
import module ModuleName ;

単一モジュール・インポート宣言import module M;は、次のパッケージ内のすべてのpublicの最上位クラスとインタフェースを必要時にインポートします。

  1. モジュールMにより、現在のモジュールにエクスポートされたパッケージ。

  2. モジュールMの読取りのために現在のモジュールによって読み取られるモジュールでエクスポートされたパッケージ。これにより、プログラムは、別のモジュールからのクラスやインタフェースを参照するモジュールのAPIを使用できるようになります。このとき、該当する別のモジュールのすべてをインポートする必要はありません。

モジュールModuleNameが現在のモジュール(7.3)によって読み取られない場合は、コンパイル時にエラーが発生します。

現在のモジュールによって読み取られるモジュールは、java.lang.moduleパッケージ仕様(7.3)で説明されているように、解決の結果によって決まります。

同じコンパイル・ユニット内の複数の単一モジュール・インポート宣言は、同じモジュールを指名できます。これらの宣言は1つを除いてすべてが重複しているとみなされます。この結果、そのモジュールは1回のみインポートされたものとして扱われます。

単一モジュール・インポート宣言は、どのソース・ファイルでも使用できます。ソース・ファイルはモジュールの一部にする必要はありません。たとえば、モジュールjava.basejava.sqlは標準Javaランタイムの一部であるため、それ自体がモジュールとして開発されていないプログラムでインポートできます。

パッケージをエクスポートしないモジュールのインポートが役立つ場合があります。これは、モジュールがパッケージをエクスポートする別のモジュールを間接的に必要とすることがあるためです。たとえば、java.seモジュールはパッケージをエクスポートしませんが、複数のモジュールを間接的に必要とするため、単一モジュール・インポート宣言import module java.se;の効果は、これらのモジュールによってエクスポートされるパッケージ(再帰的にエクスポートされるパッケージなど)をインポートすることです。

例7.5.5-1.通常のコンパイル・ユニットの単一モジュール・インポート

モジュールを使用すると、パッケージのセットをグループ化して1つの名前で再利用できるようになります。また、モジュールのエクスポートされたパッケージは、統合された一貫性のあるAPIを形成することを目的としています。単一モジュール・インポート宣言を使用すると、開発者はモジュールによってエクスポートされたすべてのパッケージを1回でインポートできるため、モジュラ・ライブラリの再利用が容易になります。次に例を示します。

import module java.xml;

これにより、モジュールjava.xmlでエクスポートされたすべてのパッケージ内で宣言されているpublicの最上位のクラスとインタフェースの単純名が、コンパイル・ユニットのクラスとインタフェースの宣言で使用できるようになります。したがって、単純名XPathは、そのクラス宣言がシャドウ化または不明瞭化されていないコンパイル・ユニットのすべての場所で、モジュールjava.xmlでエクスポートされたパッケージjavax.xml.xpathのインタフェースXPathを参照します。

モジュールM0に関連付けられている次のコンパイル・ユニットがあるとします:

package q;
import module M1;   // What does this import?
class C { ... }

このモジュールM0には、次の宣言があります:

module M0 { requires M1; }

単一モジュール・インポート宣言import module M1;の意味は、M1のエクスポートと、M1が間接的に必要とするモジュールによって異なります。例として次について考えてみます:

module M1 {
    exports p1;
    exports p2 to M0;
    exports p3 to M3;
    requires transitive M4;
    requires M5;
}

module M3 { ... }

module M4 { exports p10; }

module M5 { exports p11; }

単一モジュール・インポート宣言import module M1;の効果は次のようになります:

  1. publicの最上位のクラスとインタフェースをパッケージp1からインポートします。これは、M1がすべてに向けてp1をエクスポートするためです。

  2. publicの最上位のクラスとインタフェースをパッケージp2からインポートします。これは、M1p2をコンパイル・ユニットが関連付けられているモジュールM0にエクスポートするためです。さらに

  3. パッケージp10からpublicの最上位のクラスとインタフェースをインポートします。これは、M1p10をエクスポートするM4間接的に必要になるためです。

コンパイル・ユニットによって、パッケージp3またはp11からインポートされるものはありません。

単一モジュール・インポート宣言は、パッケージ宣言のみが含まれているソース・ファイルに記述できます。そのようなファイルは、通常、package-info.javaと呼ばれ、パッケージ・レベルの注釈とドキュメント(7.4.1)の唯一のリポジトリとして使用されます。

例7.5.5-2.モジュラ・コンパイル・ユニットの単一モジュール・インポート

インポート宣言は、モジュラ・コンパイル・ユニットにも記述されます。次のモジュラ・コンパイル・ユニットは、単一モジュール・インポート宣言を使用して、モジュールjava.sqlに関連付けられたインタフェースDriverの単純名をprovidesディレクティブで使用できるようにします:

import module java.sql;
module com.myDB.core {
    exports ...
    requires transitive java.sql;
    provides Driver with com.myDB.greatDriver;
}

モジュールMを宣言するモジュラ・コンパイル・ユニットが、モジュールMをインポートすることもできます。次の例では、クラスCの単純名をusesディレクティブで使用できることを表しています:

import module M;
module M {
    ...
    exports p;
    ...
    uses C;
    ...
}

このモジュールMによってエクスポートされたパッケージpは、次のように宣言されています:

package p;
class C { ... }

単一モジュール・インポート宣言なしの場合は、usesディレクティブでクラスCの修飾名を使用する必要があります。

次のようなモジュール宣言があるとします:

module M2 {
    requires java.se;
    exports p2;
    ...
}

このM2によってエクスポートされるパッケージp2は、次のように宣言されています:

package p2;
import module java.xml;
class MyClass {
    ...
}

モジュールM2はモジュールjava.xmlに対する依存性を直接的に表していませんが、解決プロセスによってモジュールjava.xmlはモジュールM2によって読み取られると判断されるため、モジュールjava.xmlのインポートは正しいままです。

例7.5.5-3.曖昧なインポート

複数のモジュールを明確にインポートすると、次のような名前の曖昧さが発生する可能性があります:

import module java.base;
import module java.desktop;

...
List l = ...        // Error - Ambiguous name!
...

モジュールjava.baseは、public Listインタフェースを持つパッケージjava.utilをエクスポートします。モジュールjava.desktopは、public Listクラスのパッケージjava.awtをエクスポートします。両方のモジュールをインポートすると、単純名Listの使用は明らかに曖昧になり、コンパイル時にエラーが発生します。

ただし、単一のモジュールをインポートするだけでも、次のような名前の曖昧さが発生する可能性があります:

import module java.desktop;

...
Element e = ...     // Error - Ambiguous name!
...

モジュールjava.desktopは、public Elementインタフェースとpublic Elementクラスを持つパッケージのjavax.swing.textjavax.swing.text.html.parserをそれぞれエクスポートします。そのため、単純名Elementの使用は曖昧になり、コンパイル時にエラーが発生します。

単一型インポート宣言は、名前の曖昧さを解決するために使用できます。単純名Listが曖昧な前の例は、次のように解決できます:

import module java.base;
import module java.desktop;

import java.util.List;  // Resolving the ambiguity of the simple name List

...
List l = ...            // Ok - List is resolved to java.util.List
...