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

Java®言語仕様の変更点・バージョン24+36-3646

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

コンパニオン・ドキュメントでは、モジュール・インポート宣言をサポートするためにJava仮想マシン仕様で必要となった変更点について説明します。

JEP 495で提案されたプレビュー機能の単純なソース・ファイルおよびインスタンスのmainメソッドは、この機能に依存します。

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

変更ログ:

2024-11: モジュール・インポート宣言(6.4.1)をシャドウ化する、オンデマンドでインポートされた静的型の宣言の欠落しているケースが追加されました。

2024-10: 第2プレビューの初稿。最初のプレビューからの変更:

第1章: 概要

1.5 プレビュー機能

プレビュー機能とは、次のものです:

完全に仕様が規定され、完全に実装されていますが、まだ永続的ではありません。これは、Java SEプラットフォームの特定のリリースの実装で使用でき、実際の使用に基づいた開発者のフィードバックを求めています。これにより、Java SEプラットフォームの将来のリリースで永続的になる可能性があります。

実装では、コンパイル時と実行時の両方で、Java SEプラットフォームの特定のリリースで定義されたプレビュー機能を無効にする必要があります。ただし、ユーザーがコンパイル時と実行時の両方で、ホスト・システムを介してそのプレビュー機能を有効にした場合を除きます。

Java SEプラットフォームの特定のリリースで定義されたプレビュー機能は、そのリリースのJava SEプラットフォーム仕様に列挙されています。プレビュー機能は次のように指定します:

プレビュー言語機能を使用するためのルールは次のとおりです:

一部のプレビューAPIは、Java SEプラットフォーム仕様(主にjava.lang.reflectjava.lang.invokeおよびjavax.lang.modelパッケージ)でリフレクティブとして記述されます。リフレクティブ・プレビューAPIを使用するためのルールは次のとおりです:

Java SEプラットフォーム仕様でリフレクティブとして記述されていないすべてのプレビューAPIは標準です。通常のプレビューAPIを使用するためのルールは次のとおりです:

第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全体にわたって、cの単一モジュール・インポート宣言によってインポートされたnという名前の任意の型の宣言をシャドウ化します。

特に、すべてのコンパイル・ユニットは暗黙的な宣言import java.lang.*; (7.3)を含んでいるかのように扱われるため、パッケージjava.langからインポートされたものと同じ名前を持つ、単一モジュール・インポート宣言によってインポートされた型の宣言は常にシャドウ化されます。

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

nという名前の型をインポートするパッケージpのコンパイル・ユニットc内の静的インポート・オンデマンド宣言dは、c全体にわたって、cの単一モジュール・インポート宣言によってインポートされる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
...

7.7 モジュール宣言

7.7.1 依存関係

requiresディレクティブは、現在のモジュールが依存するモジュールの名前を指定します。

java.baseモジュールの宣言にrequiresディレクティブを指定しないでください。指定した場合、このモジュールは大もとのモジュールであり、依存関係がないため、コンパイル時にエラーが発生します。(8.1.4)。

モジュールの宣言でjava.baseモジュールへの依存関係が指定されず、そのモジュール自体がjava.baseでない場合、モジュールではjava.baseモジュールへの暗黙的な依存関係が宣言されます。

requiresキーワードの後には、修飾子transitiveを指定できます。これにより、現在のモジュールをrequiresするモジュールでは、requires transitiveディレクティブで指定されたモジュールへの暗黙的な依存関係が宣言されます。

requiresキーワードの後には、修飾子staticを指定できます。これは、コンパイル時に依存関係が必須であるのに対し、実行時にはオプションであることを指定します。

モジュールの宣言でjava.baseモジュールへの依存関係が指定され、そのモジュール自体がjava.baseでない場合、a static修飾子がrequiresキーワードの後に出現すると、コンパイル時にエラーが発生します。

モジュール宣言の複数のrequiresディレクティブで同じモジュール名を指定すると、コンパイル時にエラーが発生します。

java.lang.moduleパッケージ仕様で説明されているように、現在のモジュールを唯一のルート・モジュールとする解決が、java.lang.moduleパッケージ仕様で説明されているいずれかの理由で失敗した場合、コンパイル時にエラーが発生します。

たとえば、requiresディレクティブに認識できないモジュールを指定した場合、または現在のモジュールで直接的または間接的に自身への依存関係が示されている場合です。

解決が成功した場合、その結果によって現在のモジュールで読み取られるモジュールが決まります。現在のモジュールが読み取るモジュールによって、現在のモジュールが認識できる通常のコンパイル・ユニットが決定されます(7.3)。これらの通常のコンパイル・ユニット(およびそれらの通常のコンパイル・ユニットのみ)で宣言された型は、現在のモジュール内のコードからアクセスできます(6.6)。

Java SEプラットフォームでは、明示的に宣言された名前付きモジュール(モジュール宣言付き)と、暗黙的に宣言された名前付きモジュール(自動モジュール)が区別されます。ただし、Javaプログラミング言語では区別は表面には現われません。requiresディレクティブは、明示的に宣言されているか暗黙的に宣言されているかに関係なく、名前付きモジュールを参照します。

自動モジュールは移行に便利ですが、作成者が明示的に宣言されたモジュールに変換した場合、名前とエクスポートされたパッケージが変更される可能性があるという意味で信頼性がありません。Javaコンパイラでは、requiresディレクティブが自動モジュールを参照する場合、警告を発行することが推奨されます。transitive修飾子がディレクティブに含まれている場合は、特に強い警告が推奨されます。

例7.1.1-1requires transitiveディレクティブの解決

次の4つのモジュール宣言があるとします:

module m.A {
    requires m.B;
}
module m.B {
    requires transitive m.C;
}
module m.C {
    requires transitive m.D;
}
module m.D {
    exports p;
}

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

package p;
public class Point {}

また、モジュールm.Aのパッケージclientは、エクスポートされたパッケージppublic型のPointを参照します。

package client;
import p.Point;
public class Test {
    public static void main(String[] args) {
        System.out.println(new Point());
    }
}

これらのモジュールは、現在のディレクトリにモジュールごとに1つの(含まれるモジュールにちなんで名付けられた)サブディレクトリがあると想定して、次のようにコンパイルできます:

javac --module-source-path . -d . --module m.D
javac --module-source-path . -d . --module m.C
javac --module-source-path . -d . --module m.B
javac --module-source-path . -d . --module m.A

プログラムclient.Testは次のように実行できます:

java --module-path . --module m.A/client.Test

m.Am.Dを読み取り、m.DPointを含むパッケージをエクスポートするため、m.Aのコードからm.Dのエクスポートされたpublic型のPointへの参照は有効です。解決では、m.Aが次のようにm.Dを読み取ると判断されます:

実際には、モジュールは、任意の量のリファクタをサポートするために、複数のレベルの依存関係を介して別のモジュールを読み取ることができます。モジュールが再利用できるようにリリースされると(requiresを使用して)、そのモジュールの作成者はその名前とAPIを遵守しますが、コンシューマのために元のモジュールが再利用している他のモジュールにコンテンツを自由にリファクタできます(requires transitiveを使用して)。前述の例では、パッケージpは元々m.Bによってエクスポートされている(つまり、m.A requires m.B)可能性がありますが、リファクタにより、m.Bのコンテンツの一部がm.Cおよびm.Dに移動されました。requires transitiveディレクティブのチェーンを使用することで、m.Bm.Cおよびm.Dファミリは、m.Arequiresディレクティブを変更することなく、m.Aのコードのpパッケージへのアクセスを維持できます。m.Dのパッケージpは、m.Cおよびm.Bによって再エクスポートされるのではなく、m.Am.Dを直接読み取ります。