本「 Java。A New Generation of Development 」以来、私たちは「 Project Jigsaw 」という一般名で統一されたこの言語の長年発表された新機能の開発を追ってきました。 本日、11月24日の記事の翻訳を提供します。これにより、JigsawがJava 9で行われることを十分に確信できます。
Jigsawプロジェクトが開始されてから8年が経過しました。Jigsawプロジェクトのタスクは、Javaプラットフォームをモジュール化し、共通モジュールシステムの実装に集約することです。 JigsawはJava 9で最初に登場する予定です。 このリリースは、以前はJava 7とJava 8の両方で計画されていました。Jigsawの範囲も何度も変更されています。 JavaOne 2015カンファレンスのOracleプレナリーで多くの注目を集めたJigsawと、このトピックに関するいくつかのスピーチが一度に行われたため、Jigsawの準備がほぼ完了したと信じるあらゆる理由があります。 これはあなたにとって何を意味しますか? Jigsawプロジェクトとは何ですか?
これは、モジュールシステムを簡単に紹介し、多数のコード例を使用してJigsawの動作を実証したい2つの出版物の最初のものです。 最初の部分では、モジュールのシステムとは何か、JDKがどのようにモジュール化されているかを議論し、特定の状況でのコンパイラとランタイムの動作についても検討します。
モジュールとは何ですか?
モジュールを記述するのは簡単です。これはプログラム内のユニットであり、各モジュールには3つの質問に対する回答がすぐに含まれています。 これらの回答は
module-info.java
ファイルに記録されます
module-info.java
module-info.java
各モジュールが持っています。
- モジュールの名前は何ですか?
- 彼は何をエクスポートしますか?
- これには何が必要ですか?

シンプルなモジュール
最初の質問に対する答えは簡単です。 (ほぼ)すべてのモジュールには名前があります。
de.codecentric.mymodule
などのパッケージ命名規則に準拠する必要があります
de.codecentric.mymodule
、競合を避けるため。
2番目の質問に答えるために、モジュールはこの特定のモジュールのすべてのパッケージのリストを提供します。これらのパッケージはパブリックAPIと見なされるため、他のモジュールで使用できます。 クラスがエクスポートされたパッケージでない場合は、たとえパブリックであっても、モジュールの外部から誰もアクセスできません。
3番目の質問に対する答えは、このモジュールが依存するモジュールのリストです。 これらのモジュールによってエクスポートされるすべてのパブリックタイプは、依存モジュールからアクセス可能です。 Jigsawチームは、「 read 」という別のモジュールという用語を導入しようとしています。
これは現状の大きな変化です。 Java 8までは、クラスへのパスにあるすべてのパブリックタイプは、他のタイプで使用できました。 Jigsawの出現により、Javaタイプのアクセシビリティシステムは
- 公開
- プライベート
- デフォルト
- 保護された
に
- このモジュールを読んでいる人に公開(エクスポート)
- これを読んでいるいくつかのモジュールについてはpublic(エクスポート、これについては第2部で説明します)
- このモジュール内の他のクラスにはpublic
- プライベート
- デフォルト
- 保護された
モジュール化されたJDK
モジュールの依存関係は非循環グラフを形成し、循環依存関係を回避する必要があります。 この原則を実装するために、Jigsawチームは次の大きな問題を解決する必要がありました。モジュールに侵入するJavaランタイム環境は、報告されているように、循環的および非論理的な依存関係でいっぱいです。 結果はグラフです:

グラフのベースはjava.baseです。 これは、着信エッジのみを持つ唯一のモジュールです。 作成する各モジュールは
java.base
を読み取り
java.base
java.base
宣言するかどうか-暗黙の拡張
java.lang.Object
と同様
java.lang.Object
。
java.base
java.lang
などのパッケージをエクスポートします
java.lang
、
java.util
java.util
、
java.math
java.math
など
JDKのモジュール化により、使用するJavaランタイムモジュールを指定できるようになりました。 したがって、
java.desktop
モジュールを読まない限り、アプリケーションはSwingまたはCorbaをサポートする環境を使用しないでください。
java.desktop
または
java.corba
java.corba
。 このようなトリミングされた環境の作成については、第2部で説明します。
しかし、かなりドライな理論...
ポヒミン
この記事のすべてのコードは、サンプルのコンパイル、パッケージ化、実行用のシェルスクリプトを含め、 ここから入手できます 。
ここで検討する実際的なケースは非常に単純です。 モジュール
de.codecentric.zipvalidator
があります
de.codecentric.zipvalidator
郵便番号の特定の検証を実行します。 このモジュールは
de.codecentric.addresschecker
モジュールによって読み取られます
de.codecentric.addresschecker
(郵便番号だけでなく、ここでは複雑化しないようにこれを行いません)。
zipバリデーターは、次の
module-info.java
ファイルで説明されています
module-info.java
:
module de.codecentric.zipvalidator {
de.codecentric.zipvalidator.apiをエクスポートします。
}
したがって、このモジュールはパッケージ
de.codecentric.zipvalidator.api
エクスポートします
de.codecentric.zipvalidator.api
また、他のモジュールは読み取りません(
java.base
を除く
java.base
) このモジュールは、アドレスチェッカーによって読み取られます。
module de.codecentric.addresschecker{ exports de.codecentric.addresschecker.api; requires de.codecentric.zipvalidator; }
ファイルシステムの一般的な構造は次のとおりです。
two-modules-ok/ ├── de.codecentric.addresschecker │ ├── de │ │ └── codecentric │ │ └── addresschecker │ │ ├── api │ │ │ ├── AddressChecker.java │ │ │ └── Run.java │ │ └── internal │ │ └── AddressCheckerImpl.java │ └── module-info.java ├── de.codecentric.zipvalidator │ ├── de │ │ └── codecentric │ │ └── zipvalidator │ │ ├── api │ │ │ ├── ZipCodeValidator.java │ │ │ └── ZipCodeValidatorFactory.java │ │ ├── internal │ │ │ └── ZipCodeValidatorImpl.java │ │ └── model │ └── module-info.java
モジュールは、このモジュールと同じ名前のディレクトリに配置されるという合意があります。
最初の例では、すべてが素晴らしく見えます。ルールと
AddressCheckerImpl
クラスで厳密に動作します
AddressCheckerImpl
ZipCodeValidator
のみにアピールし
ZipCodeValidator
ZipCodeValidator
および
ZipCodeValidatorFactory
ZipCodeValidatorFactory
エクスポートされたパッケージから:
public class AddressCheckerImpl implements AddressChecker { @Override public boolean checkZipCode(String zipCode) { return ZipCodeValidatorFactory.getInstance().zipCodeIsValid(zipCode); } }
ここで
javac
実行します
javac
バイトコードを生成します。
zipvalidator
をコンパイルするには
zipvalidator
(もちろん、アドレスチェッカーがzipvalidatorを読み取るため、最初に行う必要があります)
javac -d de.codecentric.zipvalidator \ $(find de.codecentric.zipvalidator -name "*.java")
zipvalidatorはユーザーモジュールに依存しないため、モジュールの話はまだありません。
find
find
.java
ファイルのリストを作成するのに役立ちます
.java
指定されたディレクトリ内。
しかし、
javac
に伝えるように
javac
モジュールの構造について、コンパイル時に これを行うには、jigsawにスイッチを入力します
modulepath
modulepath
または
-mp
-mp
。
アドレスチェッカーをコンパイルするには、次のコマンドを使用します。
javac -modulepath。 -d de.codecentric.addresschecker \
$(de.codecentric.addresschecker -name "* .java"を見つけます)
modulepathを使用して、javacにコンパイル済みモジュール(この場合はこれ)の場所を指示します。クラスパススイッチに似たものを取得します。
ただし、複数のモジュールを個別にコンパイルするのは少し面倒なようです-他の-modulesourcepathスイッチを使用して複数のモジュールを一度にコンパイルする方が良いでしょう:
javac -d . -modulesourcepath . $(find . -name "*.java")
このコードは、すべてのサブディレクトリを検索します。 モジュールのディレクトリ。モジュールに含まれるすべてのJavaファイルをコンパイルします。
すべてをコンパイルしたら、自然に何が起こったのか試してみたい:
java -mp . -m de.codecentric.addresschecker/de.codecentric.addresschecker.api.Run 76185
繰り返しますが、モジュールへのパスを示し、コンパイルされたモジュールの場所をJVMに伝えます。 また、メインクラス(およびパラメーター)も設定します。
やあ、結論はこうだ:
76185 is a valid zip code
モジュラージャー
ご存じのとおり、Javaの世界では、jarファイルでバイトコードを送受信することに慣れています。 Jigsawは、モジュール式jarの概念を導入しています。 モジュラーjarは通常のjarに非常に似ていますが、コンパイルされた
module-info.class.
も含まれてい
module-info.class.
module-info.class.
これらのファイルが目的のターゲットバージョン用にコンパイルされている場合、これらのアーカイブには下位互換性があります。
module-info.java
有効な型名ではないため、コンパイルされた
module-info.class
module-info.class
古いJVMでは無視されます。
zipvalidatorのjarを作成するには、次のように記述します。
jar --create --file bin/zipvalidator.jar \ --module-version=1.0 -C de.codecentric.zipvalidator
。
出力ファイル、バージョン(実行時のJigsawでのモジュールのいくつかのバージョンの使用は個別に指定されていませんが)、およびパッケージ化されるモジュールを示します。
addresscheckerにはメインクラスもあるため、指定できます。
jar --create --file=bin/addresschecker.jar --module-version=1.0 \ --main-class=de.codecentric.addresschecker.api.Run \ -C de.codecentric.addresschecker .
メインクラスは
module-info.java
指定されていません
module-info.java
、ご想像のとおり(最初はJigsawチームがそうする予定でした)が、通常はマニフェストに記述されています。
この例を実行すると
java -mp bin -m de.codecentric.addresschecker 76185
前の場合と同じ答えが得られます。 再びモジュールへのパスを指定します。この場合、jarを書き込んだbinディレクトリに移動します。 addresschecker.jarマニフェストにはすでにこの情報があるため、メインクラスを指定する必要はありません。 モジュール名を
-m
スイッチに指定するだけです
-m
。
これまでのところ、すべてが簡単で快適です。 次に、モジュールを少しいじって、無秩序になり始めた場合のコンパイルと実行中のジグソーの動作を見てみましょう。
エクスポートされていないタイプの使用
この例では、使用してはならない別のモジュールから型にアクセスするとどうなるかを見てみましょう。
AddressCheckerImpl
このファクトリーに飽き飽きしているから
AddressCheckerImpl
、実装を変更します
return new ZipCodeValidatorImpl().zipCodeIsValid(zipCode);
コンパイルしようとすると、期待どおりになります
error: ZipCodeValidatorImpl is not visible because package de.codecentric.zipvalidator.internal is not visible
そのため、コンパイル時にエクスポートされない型を使用すると機能しません。
しかし、私たちは賢い人なので、少しごまかしてリフレクションを使用します。
ClassLoader classLoader = AddressCheckerImpl.class.getClassLoader(); try { Class aClass = classLoader.loadClass("de.[..].internal.ZipCodeValidatorImpl"); return ((ZipCodeValidator)aClass.newInstance()).zipCodeIsValid(zipCode); } catch (Exception e) { throw new RuntimeException(e); }
完全にコンパイルされた、実行しましょう。 しかし、いや、ジグソーをだますのは簡単ではありません。
java.lang.IllegalAccessException: class de.codecentric.addresschecker.internal.AddressCheckerImpl (in module de.codecentric.addresschecker) cannot access class [..].internal.ZipCodeValidatorImpl (in module de.codecentric.zipvalidator) because module de.codecentric.zipvalidator does not export package de.codecentric.zipvalidator.internal to module de.codecentric.addresschecker
したがって、Jigsawには、コンパイル時だけでなく、実行時のチェックも含まれます! そして、私たちが間違ったことを非常に明確に教えてくれます。
循環依存
次の場合、アドレスチェッカーモジュールAPIにzipvalidatorが使用できるクラスが含まれていることが突然わかりました。 怠zyなので、クラスを別のモジュールにリファクタリングする代わりに、addresscheckerの依存関係を宣言します。
module de.codecentric.zipvalidator{ requires de.codecentric.addresschecker; exports de.codecentric.zipvalidator.api; }
循環的な依存関係は定義上禁止されているため、コンパイラは私たちの邪魔になります(共通の目的のため)。
./de.codecentric.zipvalidator/module-info.java:2: error: cyclic dependence involving de.codecentric.addresschecker
これを行うことはできません。コンパイル中であっても、事前に警告されます。
暗黙の読みやすさ
機能を拡張するために、新しいモジュール
de.codecentric.zipvalidator.model
導入してzipvalidatorを継承することにしました
de.codecentric.zipvalidator.model
妥当なブール値だけでなく、検証結果の特定のモデルが含まれています。 新しいファイル構造は次のとおりです。
three-modules-ok/ ├── de.codecentric.addresschecker │ ├── de │ │ └── codecentric │ │ └── addresschecker │ │ ├── api │ │ │ ├── AddressChecker.java │ │ │ └── Run.java │ │ └── internal │ │ └── AddressCheckerImpl.java │ └── module-info.java ├── de.codecentric.zipvalidator │ ├── de │ │ └── codecentric │ │ └── zipvalidator │ │ ├── api │ │ │ ├── ZipCodeValidator.java │ │ │ └── ZipCodeValidatorFactory.java │ │ └── internal │ │ └── ZipCodeValidatorImpl.java │ └── module-info.java ├── de.codecentric.zipvalidator.model │ ├── de │ │ └── codecentric │ │ └── zipvalidator │ │ └── model │ │ └── api │ │ └── ZipCodeValidationResult.java │ └── module-info.java
クラス
ZipCodeValidationResult
ZipCodeValidationResult
-「短すぎる」、「長すぎる」などの形式のインスタンスを持つ単純な列挙
クラス
module-info.java
module-info.java
このように継承しました:
module de.codecentric.zipvalidator{ exports de.codecentric.zipvalidator.api; requires de.codecentric.zipvalidator.model; }
これで、ZipCodeValidatorの実装は次のようになります。
@Override public <strong>ZipCodeValidationResult</strong> zipCodeIsValid(String zipCode) { if (zipCode == null) { return ZipCodeValidationResult.ZIP_CODE_NULL; [snip] } else { return ZipCodeValidationResult.OK; } }
addresscheckerモジュールは、戻り値の型として列挙型を取得できるようになりましたので、続行できますか? いや! コンパイルの結果:
./de.codecentric.addresschecker/de/[..]/internal/AddressCheckerImpl.java:5: error: ZipCodeValidationResult is not visible because package de.codecentric.zipvalidator.model.api is not visible
アドレスチェッカーのコンパイル中にエラーが発生しました-zipvalidatorは、公開APIでzipvalidatorモデルからエクスポートされたタイプを使用します。 アドレスチェッカーはこのモジュールを読み取らないため、このタイプにアクセスできません。
この問題には2つの解決策があります。 明らかに:アドレスチェッカーからzipvalidatorモデルに読み取りエッジを追加します。 ただし、これは滑りやすい勾配です。zipvalidatorでの作業にのみ必要な場合、なぜこの依存関係を宣言する必要があるのでしょうか。 zipvalidatorは、必要なすべてのモジュールにアクセスできることを保証すべきではありませんか? すべきであるかもしれない-ここで私たちは暗黙の読みやすさになります。 publicキーワードを必要な定義に追加することにより、すべてのクライアントモジュールに別のモジュールも読み込む必要があることを伝えます。 例として、更新された
module-info.java
クラスを考えます
module-info.java
zipvalidator:
module de.codecentric.zipvalidator{ exports de.codecentric.zipvalidator.api; requires public de.codecentric.zipvalidator.model; }
public
キーワード
public
zipvalidatorを読み取るすべてのモジュールに、モデルも読み取る必要があることを伝えます。 クラスパスを別の方法で操作する必要がありました。たとえば、すべての依存関係もクライアントからアクセス可能であることを保証する必要がある場合、Maven POMに依存できませんでした。 これを実現するには、パブリックAPIの一部である場合は明示的に指定する必要がありました。 これは非常に美しいモデルです。クラス内でのみ依存関係を使用する場合、クライアントにとって重要なことは何ですか? また、クラス外で使用する場合は、直接報告する必要もあります。
まとめ
それで最初の部分は終わりました。 モジュールごとに回答する必要がある3つの質問と、Javaランタイムのモジュール化について説明しました。 次に、2つのモジュールで構成される単純なJavaアプリケーションをコンパイル、起動、およびパッケージ化する例を見てみました。 次に、実際の例を使用して、確立されたルールの違反にモジュールシステムがどのように応答するかを調べました。 さらに、機能を拡張して、3番目のモジュールを検討し、暗黙の読みやすさの概念について説明しました。
次のセクションでは、次の問題に対処します。
- モジュールパスに同じ名前の複数のモジュールが含まれている場合、Jigsawはどのように機能しますか?
- モジュールへのパスに異なるモジュールがあり、同じパッケージをエクスポートするとどうなりますか?
- モジュール化されていない継承された依存関係をどうしますか?
- ランタイムの独自の切り捨てバージョンを作成する方法は?