java 应用分模块_在Java 11中创建一个简单的模块化应用教程
模塊化編程使人們能夠?qū)⒋a組織成獨(dú)立的,有凝聚力的模塊,這些模塊可以組合在一起以實(shí)現(xiàn)所需的功能。
本文摘自Nick Samoylov和Mohamed Sanaulla撰寫的一本名為Java 11 Cookbook - Second Edition的書。在本書中,您將學(xué)習(xí)如何使用Java?11中的類和接口實(shí)現(xiàn)面向?qū)ο蟮脑O(shè)計(jì)?。
可以在GitHub上找到本教程中顯示的示例的完整代碼。
您應(yīng)該想知道這種模塊化是什么,以及如何使用Java創(chuàng)建模塊化應(yīng)用程序?。在本文中,我們將通過一個(gè)簡單的示例來嘗試清除在Java中創(chuàng)建模塊化應(yīng)用程序的困惑?。我們的目標(biāo)是向您展示如何創(chuàng)建模塊化應(yīng)用程序;?因此,我們選擇了一個(gè)簡單的例子,以便專注于我們的目標(biāo)。
做什么
我們的示例是一個(gè)簡單的高級計(jì)算器,它檢查數(shù)字是否為素?cái)?shù),計(jì)算素?cái)?shù)之和,檢查數(shù)字是否為偶數(shù),并計(jì)算偶數(shù)和奇數(shù)之和。
做好準(zhǔn)備
我們將應(yīng)用程序分為兩個(gè)模塊:
math.util模塊包含用于執(zhí)行數(shù)學(xué)計(jì)算的API
calculator模塊啟動(dòng)了一個(gè)高級計(jì)算器
怎么做
1.?讓我們實(shí)現(xiàn)com.packt.math.MathUtil中的API,從isPrime(Integer number)API開始:public static Boolean isPrime(Integer number){
if ( number == 1 ) { return false; }
return IntStream.range(2,num).noneMatch(i -> num % i == 0 );
}
2. 實(shí)現(xiàn)sumOfFirstNPrimes(Integer count)
public static Integer sumOfFirstNPrimes(Integer count){
return IntStream.iterate(1,i -> i+1)
.filter(j -> isPrime(j))
.limit(count).sum();
}
3.?讓我們寫一個(gè)函數(shù)來檢查數(shù)字是否是偶數(shù):
public static Boolean isEven(Integer number){
return number % 2 == 0;
}
4. 非isEven結(jié)果告訴我們這個(gè)數(shù)字是否是奇數(shù)。我們可以使用函數(shù)來查找前N個(gè)偶數(shù)和前N個(gè)奇數(shù)之和,如下所示:
public static Integer sumOfFirstNEvens(Integer count){
return IntStream.iterate(1,i -> i+1)
.filter(j -> isEven(j))
.limit(count).sum();
}
public static Integer sumOfFirstNOdds(Integer count){
return IntStream.iterate(1,i -> i+1) .filter(j -> !isEven(j)) .limit(count).sum();
}
我們可以在前面的API中看到重復(fù)以下操作:
從數(shù)字1開始的無限數(shù)字序列
根據(jù)某些條件過濾數(shù)字
將流的數(shù)量限制為給定的計(jì)數(shù)
找到由此獲得的數(shù)字之和
根據(jù)我們的觀察,我們可以重構(gòu)前面的API并將這些操作提取到一個(gè)方法中,如下所示:
Integer computeFirstNSum(Integer count,
IntPredicate filter){
return IntStream.iterate(1,i ?- > i + 1)
.filter(filter)
.limit(count).sum();
}
這里??count是我們需要找到的總和的數(shù)量限制,并且??filter是選擇求和數(shù)的條件。
讓我們根據(jù)剛剛進(jìn)行的重構(gòu)重寫API:
public static Integer sumOfFirstNPrimes(Integer count){ return computeFirstNSum(count, (i -> isPrime(i))); }
public static Integer sumOfFirstNEvens(Integer count){ return computeFirstNSum(count, (i -> isEven(i))); } public static Integer sumOfFirstNOdds(Integer count){ return computeFirstNSum(count, (i -> !isEven(i)));
到目前為止,我們已經(jīng)看到了一些圍繞數(shù)學(xué)計(jì)算的API。
開始正題
讓我們將這個(gè)小實(shí)用程序類作為名為的模塊的一部分??math.util。以下是我們用于創(chuàng)建模塊的一些約定:
將與模塊相關(guān)的所有代碼放在一個(gè)名為的目錄下math.util,并將其視為我們的模塊根目錄。
在根文件夾中,插入名為module-info.java.的文件
將包和代碼文件放在根目錄下。
module-info.java包含什么?
模塊的名稱
它導(dǎo)出的包,即可供其他模塊使用的包
它依賴的模塊
它使用的服務(wù)
它為其提供實(shí)施的服務(wù)
我們的math.util模塊不依賴于任何其他模塊(當(dāng)然,java.base模塊除外)。但是,它使其API可用于其他模塊(如果沒有,那么這個(gè)模塊的存在是有問題的)。讓我們繼續(xù)把這個(gè)陳述放到代碼中:
module math.util {
exports com.packt.math;
}
我們告訴Java編譯器和運(yùn)行時(shí)我們的math.util?模塊正在將com.packt.math包中的代碼導(dǎo)出到任何依賴的模塊math.util。
可以在以下位置找到此模塊的代碼??Chapter03/2_simple-modular-math-util/math.util。
現(xiàn)在,讓我們創(chuàng)建另一個(gè)使用該math.util模塊的模塊計(jì)算器。該模塊有一個(gè)Calculator類,其工作是接受用戶選擇執(zhí)行哪個(gè)數(shù)學(xué)運(yùn)算,然后執(zhí)行操作所需的輸入。用戶可以從五種可用的數(shù)學(xué)運(yùn)算中進(jìn)行選擇:
素?cái)?shù)檢查
偶數(shù)號(hào)檢查
N素?cái)?shù)總和
N偶數(shù)總和
N奇數(shù)總和
我們在代碼中看到這個(gè):
private static Integer acceptChoice(Scanner reader){
System.out.println("************Advanced Calculator************");
System.out.println("1. Prime Number check");
System.out.println("2. Even Number check");
System.out.println("3. Sum of N Primes");
System.out.println("4. Sum of N Evens");
System.out.println("5. Sum of N Odds");
System.out.println("6. Exit");
System.out.println("Enter the number to choose operation");
return reader.nextInt();
}
然后,對于每個(gè)選項(xiàng),我們接受所需的輸入并調(diào)用相應(yīng)的MathUtilAPI,如下所示:
switch(choice){
case 1:
System.out.println("Enter the number");
Integer number = reader.nextInt();
if (MathUtil.isPrime(number)){
System.out.println("The number "+ number +" is prime");
}else{
System.out.println("The number "+ number +" is not prime");
}
break;
case 2:
System.out.println("Enter the number");
Integer number = reader.nextInt();
if (MathUtil.isEven(number)){
System.out.println("The number "+ number +" is even");
}
break;
case 3:
System.out.println("How many primes?");
Integer count = reader.nextInt();
System.out.println(String.format("Sum of %d primes is %d",
count, MathUtil.sumOfFirstNPrimes(count)));
break;
case 4:
System.out.println("How many evens?");
Integer count = reader.nextInt();
System.out.println(String.format("Sum of %d evens is %d",
count, MathUtil.sumOfFirstNEvens(count)));
break;
case 5:
System.out.println("How many odds?");
Integer count = reader.nextInt();
System.out.println(String.format("Sum of %d odds is %d",
count, MathUtil.sumOfFirstNOdds(count)));
break;
}
讓我們calculator以與為模塊創(chuàng)建模塊相同的方式為模塊創(chuàng)建模塊定義math.util:
module calculator{
requires math.util;
}
在前面的模塊定義中,我們提到??calculator模塊依賴于??math.util模塊使用??required?關(guān)鍵字。
讓我們編譯代碼:
javac -d mods --module-source-path . $(find . -name "*.java")
--module-source-path?命令是??javac新的命令行選項(xiàng),用于指定模塊源代碼的位置。
讓我們執(zhí)行前面的代碼:
java --module-path mods -m calculator/com.packt.calculator.Calculator
--module-path?命令類似于--classpath,是新java的命令行選項(xiàng)???,指定已編譯模塊的位置。
運(yùn)行上述命令后,您將看到計(jì)算器正在運(yùn)行。
我們提供了腳本來測試Windows和Linux平臺(tái)上的代碼?。請使用run.bat用于Windows和run.sh用于?Linux的。
原理
現(xiàn)在您已經(jīng)完成了示例,我們將了解如何對其進(jìn)行概括,以便我們可以在所有模塊中應(yīng)用相同的模式。我們遵循特定的約定來創(chuàng)建模塊:
| application_root_directory
| --module1_root
| ---- module-info.java
| ---- com
| ------ packt
| -------- sample
| --------- -MyClass.java
| --module2_root
| ---- module-info.java
| ---- com
| ------ packt
| -------- test
| ------- ---MyAnotherClass.java
我們將特定于模塊的代碼放在其文件夾中,并在文件夾module-info.java?的根目錄下放置相應(yīng)的文件。這樣,代碼組織得很好。
{Annotation} [open] module ModuleName {{ModuleStatement}}
這是語法,解釋如下:
{Annotation}:這是表單的任何注釋@Annotation(2)。
open:此關(guān)鍵字是可選的。開放模塊通過反射在運(yùn)行時(shí)訪問其所有組件。但是,在編譯時(shí)和運(yùn)行時(shí),只能訪問顯式導(dǎo)出的那些組件。
module:這是用于聲明模塊的關(guān)鍵字。
ModuleName:這是模塊的名稱,該模塊是有效的Java標(biāo)識(shí)符,.在標(biāo)識(shí)符名稱之間允許使用dot() - 類似于??math.util。
{ModuleStatement}:這是模塊定義中允許的語句的集合。讓我們接下來展開。
模塊語句具有以下形式:
ModuleStatement:
requires {RequiresModifier} ModuleName ;
exports PackageName [to ModuleName {, ModuleName}] ;
opens PackageName [to ModuleName {, ModuleName}] ;
uses TypeName ;
provides TypeName with TypeName {, TypeName} ;
模塊語句在這里被解碼:
requires:這用于聲明對模塊的依賴。{RequiresModifier}可以是傳遞的,靜態(tài)的,或兩者兼而有之。傳遞意味著依賴于給定模塊的任何模塊也隱式地依賴于給定模塊傳遞所需的模塊。靜態(tài)意味著模塊依賴在編譯時(shí)是必需的,但在運(yùn)行時(shí)是可選的。一些例子是??requires math.util,requires transitive math.util和??requires static math.util。
exports:這用于使依賴模塊可以訪問給定的包。或者,我們可以通過指定模塊名稱來強(qiáng)制包對特定模塊的可訪問性,例如??exports com.package.math to claculator。
opens:這用于打開特定包。我們之前看到,我們可以通過open使用模塊聲明指定關(guān)鍵字來打開模塊。但這可能是限制性較小的。因此,為了使其更具限制性,我們可以使用openskeyword-?在運(yùn)行時(shí)打開一個(gè)特定的反射訪問包opens com.packt.math。
uses:這用于聲明可通過可訪問的服務(wù)接口的依賴項(xiàng)java.util.ServiceLoader。服務(wù)接口可以位于當(dāng)前模塊中,也可以位于當(dāng)前模塊所依賴的任何模塊中。
provides:這用于聲明服務(wù)接口并為其提供至少一個(gè)實(shí)現(xiàn)。可以在當(dāng)前模塊或任何其他相關(guān)模塊中聲明服務(wù)接口。但是,必須在同一模塊中提供服務(wù)實(shí)現(xiàn);?否則,將發(fā)生編譯時(shí)錯(cuò)誤。
我們將在使用服務(wù)中更詳細(xì)地查看uses和provides子句,??以在消費(fèi)者和提供者模塊??配方之間創(chuàng)建松散耦合。
可以使用--module-source-path命令行選項(xiàng)一次編譯所有模塊的模塊源。這樣,所有模塊都將被編譯并放置在該-d選項(xiàng)提供的目錄下的相應(yīng)目錄中。例如,??javac -d mods?--module-source-path . $(find . -name "*.java")?將當(dāng)前目錄中的代碼編譯到mods?目錄中。
運(yùn)行代碼同樣簡單。我們使用命令行選項(xiàng)指定編譯所有模塊的路徑??--module-path。然后,我們使用命令行選項(xiàng)提及模塊名稱以及完全限定的主類名稱??-m,例如??java --module-path mods?-m calculator/com.packt.calculator.Calculator。
總結(jié)
以上是生活随笔為你收集整理的java 应用分模块_在Java 11中创建一个简单的模块化应用教程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: css打印适应纸张_从生态平衡到打印机故
- 下一篇: Java集合之LinkedHashMap