在 Groovy 和 Java 中創建並初始化映射的不同
我最近在探索 Java 與 Groovy 在 創建並初始化 列表 和 在運行時構建 列表 方面的一些差異。我觀察到,就實現這些功能而言,Groovy 的簡潔和 Java 的繁複形成了鮮明對比。
在這篇文章中,我將實現在 Java 和 Groovy 中創建並初始化 映射 。映射為開發支持根據 關鍵字 檢索的結構提供了可能,如果找到了這樣一個關鍵字,它就會返回對應的 值 。今天,很多編程語言都實現了映射,其中包括 Java 和 Groovy,也包括了 Python(它將映射稱為 字典 )、Perl、awk 以及許多其他語言。另一個經常被用來描述映射的術語是 關聯數組 ,你可以在 這篇維基百科文章 中了解更多。Java 和 Groovy 中的映射都是非常通用的,它允許關鍵字和值為任意類型,只要繼承了 Object
類即可。
安裝 Java 和 Groovy
Groovy 基於 Java,因此你需要先安裝 Java。你的 Linux 發行版的倉庫中可能有最近的比較好的 Java 和 Groovy 版本。或者,你也可以在根據上面鏈接中的指示來安裝 Groovy。對於 Linux 用戶來說,SDKMan 是一個不錯的代替選項,你可以使用它來獲取多個 Java 和 Groovy 版本,以及許多其他的相關工具。在這篇文章中,我使用的 SDK 發行版是:
- Java: version 11.0.12-open of OpenJDK 11;
- Groovy: version 3.0.8.
言歸正傳
Java 提供了非常多的方式來實例化和初始化映射,並且從 Java 9 之後,添加了一些新的方式。其中最明顯的方式就是使用 java.util.Map.of()
這個靜態方法,下面介紹如何使用它:
var m1 = Map.of(
"AF", "Afghanistan",
"AX", "Åland Islands",
"AL", "Albania",
"DZ", "Algeria",
"AS", "American Samoa",
"AD", "Andorra",
"AO", "Angola",
"AI", "Anguilla",
"AQ", "Antarctica");
System.out.println("m1 = " + m1);
System.out.println("m1 is an instance of " + m1.getClass());
事實證明,在此種情況下,Map.of()
有兩個重要的限制。其一,這樣創建出來的映射實例是 不可變的 。其二,你最多只能提供 20 個參數,用來表示 10 個 鍵值對 。
你可以嘗試著添加第 10 對和第 11 對,比方說 "AG", "Antigua and Barbuda" 和 "AR", "Argentina",然後觀察會發生什麼。你將發現 Java 編譯器嘗試尋找一個支持 11 個鍵值對的 Map.of()
方法而遭遇失敗。
快速查看 java.util.Map 類的文檔,你就會找到上述第二個限制的原因,以及解決這個難題的一種方式:
var m2 = Map.ofEntries(
Map.entry("AF", "Afghanistan"),
Map.entry("AX", "Åland Islands"),
Map.entry("AL", "Albania"),
Map.entry("DZ", "Algeria"),
Map.entry("AS", "American Samoa"),
Map.entry("AD", "Andorra"),
Map.entry("AO", "Angola"),
Map.entry("AI", "Anguilla"),
Map.entry("AQ", "Antarctica"),
Map.entry("AG", "Antigua and Barbuda"),
Map.entry("AR", "Argentina"),
Map.entry("AM", "Armenia"),
Map.entry("AW", "Aruba"),
Map.entry("AU", "Australia"),
Map.entry("AT", "Austria"),
Map.entry("AZ", "Azerbaijan"),
Map.entry("BS", "Bahamas"),
Map.entry("BH", "Bahrain"),
Map.entry("BD", "Bangladesh"),
Map.entry("BB", "Barbados")
);
System.out.println("m2 = " + m2);
System.out.println("m2 is an instance of " + m2.getClass());
這就是一個比較好的解決方式,前提是我不在隨後的代碼里改變使用 Map.ofEntries()
創建並初始化的映射內容。注意,我在上面使用了 Map.ofEntries()
來代替 Map.of()
。
然而,假設我想要創建並初始化一個非空的映射,隨後往這個映射中添加數據,我需要這樣做:
var m3 = new HashMap<String,String>(Map.ofEntries(
Map.entry("AF", "Afghanistan"),
Map.entry("AX", "Åland Islands"),
Map.entry("AL", "Albania"),
Map.entry("DZ", "Algeria"),
Map.entry("AS", "American Samoa"),
Map.entry("AD", "Andorra"),
Map.entry("AO", "Angola"),
Map.entry("AI", "Anguilla"),
Map.entry("AQ", "Antarctica"),
Map.entry("AG", "Antigua and Barbuda"),
Map.entry("AR", "Argentina"),
Map.entry("AM", "Armenia"),
Map.entry("AW", "Aruba"),
Map.entry("AU", "Australia"),
Map.entry("AT", "Austria"),
Map.entry("AZ", "Azerbaijan"),
Map.entry("BS", "Bahamas"),
Map.entry("BH", "Bahrain"),
Map.entry("BD", "Bangladesh"),
Map.entry("BB", "Barbados")
));
System.out.println("m3 = " + m3);
System.out.println("m3 is an instance of " + m3.getClass());
m3.put("BY", "Belarus");
System.out.println("BY: " + m3.get("BY"));
這裡,我把使用 Map.ofEntries()
創建出來的不可變映射作為 HashMap
的一個構造參數,以此創建了該映射的一個 可變副本 ,之後我就可以修改它 —— 比如使用 put()
方法。
讓我們來看看上述過程如何用 Groovy 來實現:
def m1 = [
"AF": "Afghanistan",
"AX": "Åland Islands",
"AL": "Albania",
"DZ": "Algeria",
"AS": "American Samoa",
"AD": "Andorra",
"AO": "Angola",
"AI": "Anguilla",
"AQ": "Antarctica",
"AG": "Antigua and Barbuda",
"AR": "Argentina",
"AM": "Armenia",
"AW": "Aruba",
"AU": "Australia",
"AT": "Austria",
"AZ": "Azerbaijan",
"BS": "Bahamas",
"BH": "Bahrain",
"BD": "Bangladesh",
"BB": "Barbados"]
println "m1 = $m1"
println "m1 is an instance of ${m1.getClass()}"
m1["BY"] = "Belarus"
println "m1 = $m1"
只看一眼,你就會發現 Groovy 使用了 def
關鍵字而不是 var
—— 儘管在 最近模型 的 Groovy(version 3+)中,使用 var
關鍵字也是可行的。
你還會發現,你是通過在括弧里添加了一個鍵值對列表來創建一個映射的。不僅如此,這樣創建的列表對象還非常有用,這裡有幾個原因。其一,它是可變的;其二,它是一個 LinkedHashMap
的實例,內部維持了數據的插入順序。所以,當你運行 Java 版本的代碼並列印出變數 m3
,你會看到:
m3 = {BB=Barbados, BD=Bangladesh, AD=Andorra, AF=Afghanistan, AG=Antigua and Barbuda, BH=Bahrain, AI=Anguilla, AL=Albania, AM=Armenia, AO=Angola, AQ=Antarctica, BS=Bahamas, AR=Argentina, AS=American Samoa, AT=Austria, AU=Australia, DZ=Algeria, AW=Aruba, AX=Åland Islands, AZ=Azerbaijan}
而當你運行 Groovy 版本的代碼,你會看到:
m1 = [AF:Afghanistan, AX:Åland Islands, AL:Albania, DZ:Algeria, AS:American Samoa, AD:Andorra, AO:Angola, AI:Anguilla, AQ:Antarctica, AG:Antigua and Barbuda, AR:Argentina, AM:Armenia, AW:Aruba, AU:Australia, AT:Austria, AZ:Azerbaijan, BS:Bahamas, BH:Bahrain, BD:Bangladesh, BB:Barbados]
再一次,你將看到 Groovy 是如何簡化事情的。這樣的語法非常直觀,有點像 Python 里的字典,並且,即使你有一個超過 10 個鍵值對的初始列表,你也不需要去記住各種必要的彆扭方式。注意我們使用的表達式:
m1[「BY」] = 「Belarus」
而在 Java 中,你需要這樣做:
m1.put(「BY」, 「Belarus」)
還有,這個映射默認是可變的,這麼做的利弊很難評判,還是得取決於你的需求是什麼。我個人覺得,Java 在這種情況下的 「默認不可變」 機制,最讓我困擾的地方是,它沒有一個類似於 Map.mutableOfMutableEntries()
的方法。這迫使一些剛學會如何聲明和初始化一個映射的程序員,不得不轉念去思考該如何把他們手中不可變的映射,轉換為可變的。同時我也想問,創建一個不可變的對象然後再捨棄它,這樣真的好嗎?
另一個值得考慮的事情是,Groovy 使用方括弧代替 Java 中的 put()
和 get()
方法來進行關鍵字查找。因此你可以這樣寫:
m1[「ZZ」] = m1[「BY」]
而不需要這樣寫:
m1.put(「ZZ」,m1.get(「BY」))
有時候,就像使用某個類的實例變數一樣來使用映射中的關鍵字和值是一個好辦法。設想你現在有一堆想要設置的屬性,在 Groovy 中,它們看起來就像下面這樣:
def properties = [
verbose: true,
debug: false,
logging: false]
然後,你可以改變其中的某個屬性,就像下面這樣:
properties.verbose = false
之所以這樣能工作,是因為,只要關鍵字元合特定的規則,你就可以省略引號,然後直接用點操作符來代替方括弧。儘管這個功能非常有用,也非常好用,它也同時也意味著,如果你要把一個變數作為一個映射的關鍵字來使用,你就必須把這個變數包裹在圓括弧里,就像下面這樣:
def myMap = [(k1): v1, (k2): v2]
是時候告訴勤奮的讀者 Groovy 是一門為編寫腳本而量身定製的語言了。映射通常是腳本中的關鍵元素,它為腳本提供了 查找表 ,並且通常起到了作為內存資料庫的作用。我在這裡使用的例子是 ISO 3166 規定的兩個字母的國家代碼和國家名稱。對在世界上各個國家的互聯網使用者來說,這些代碼是很熟悉的。此外,假設我們要編寫一個從日誌文件中查找互聯網主機名,並藉此來了解用戶的地理位置分布的腳本工具,那麼這些代碼會是十分有用的部分。
Groovy 相關資源
Apache Groovy 網站 上有非常多的文檔。另一個很棒的 Groovy 資源是 Mr. Haki。Baeldung 網站 提供了大量 Java 和 Groovy 的有用教程。學習 Groovy 還有一個很棒的原因,那就是可以接著學習 Grails,後者是一個優秀的、高效率的全棧 Web 框架。它基於許多優秀組件構建而成,比如有 Hibernate、Spring Boot 和 Micronaut 等。
via: https://opensource.com/article/22/3/maps-groovy-vs-java
作者:Chris Hermansen 選題:lujun9972 譯者:lkxed 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive