搞定Java Map,先從這進入簡介Map函數式應用
Java Map接口,即java.util.Map,表示鍵和值之間的映射。 更具體地說,Java Map可以存儲鍵和值對。 每個鍵都鏈接到一個特定的值。 存儲在Map中後,可以僅使用鍵查找值。
Java Map接口不是Collection接口的子類型。 因此它的行為與其他集合類型略有不同。
1.Map實現
由於Map是一個接口,因此需要實例化Map接口的具體實現才能使用它。Java 集合 API包含以下Map實現:
· java.util.HashMap
· java.util.Hashtable
· java.util.EnumMap
· java.util.IdentityHashMap
· java.util.LinkedHashMap
· java.util.Properties
· java.util.TreeMap
· java.util.WeakHashMap
根據我的經驗,最常用的Map實現是HashMap和TreeMap。
這些Map實現中的每一個在迭代Map時的元素順序和在映射中插入和訪問元素所花費的時間(大O表示法)的行為略有不同。
HashMap映射鍵和值。 它不保證map內部存儲的元素的任何順序。
TreeMap也映射鍵和值。 此外,它保證了迭代鍵或值的順序——這是鍵或值的排序順序。 查看Java Map JavaDoc以獲取更多詳細信息。
以下是如何創建Map實例的幾個示例:
Map mapA = new HashMap();
Map mapB = new TreeMap();
2.Map應用示例
在JavaMap中插入元素
要向Map添加元素,請調用其put()方法。 這裡有一些例子,如下所示:
Map mapA = new HashMap();
mapA.put("key1", "element 1");
mapA.put("key2", "element 2");
mapA.put("key3", "element 3");
上面三個put()調用,將字符串值映射到字符串鍵。 然後,您可以使用該鍵獲取值,我們將在下一節中看到。
2.1.Map中只能插入對象
只有Java對象可以用作Java Map中的鍵和值。 如果您將原始值(例如int,double等)作為鍵或值傳遞給Map,則原始值將在作為參數傳遞之前自動裝箱。 以下是傳遞給put()方法的自動裝箱原始參數的示例:
mapA.put("key", 123);
傳遞給上例中put()方法的值是一個原始int。 但因為put()方法需要一個Oject實例作為鍵和值,故Java把基本類型數據自動裝箱成Integer對象實例。 如果將基元作為鍵傳遞給put()方法,也會發生自動裝箱。
小提示:所以,在面試被問這個問題時,你要知道怎麼回答.
Map中後續操作中插入相同的鍵會怎樣?
給定的鍵只能在Map中出現一次。 這意味著,每個鍵只有一個鍵值對可以同時存在於Map中。 換句話說,對於鍵"key1",只有一個值可以存儲在同一個Map實例中。 當然,您可以在不同的Map實例中存儲相同鍵的值。
如果使用相同的鍵多次調用put(),則通過put()傳遞給該鍵的最新值將覆蓋已存儲在Map中該鍵的值。 換句話說,最新值替換給定鍵的現有值。
2.2.Map空值(Key或Value)會怎樣
不允許空鍵(Key)
請注意,key不能為空! Map使用鍵的hashCode()和equals()方法在內部存儲鍵值對,因此如果鍵為null,則Map無法在內部正確放置鍵值對。
允許空值(Value)
存儲在Map中的鍵值對的值允許為空,因此下面語句是完全有效的:
mapA.put("D", null);
請記住,當使用該鍵調用get()時,將獲得null——存儲的就是null
Object value = mapA.get("D");
如果先前為此鍵插入了空值(如上例所示),則在執行此代碼後,value變量的值為null。
2.3.插入另一個Map的所有元素
Java Map接口有一個名為putAll()的方法,它可以將來自另一個Map實例的所有鍵值對(條目)複製到自身中。 在集合論中,這也稱為兩個Map實例的並集。
以下是通過putAll()將所有條目從一個Java Map複製到另一個Java Map的示例:
Map mapA = new HashMap();
mapA.put("key1", "value1");
mapA.put("key2", "value2");
Map mapB = new HashMap();
mapB.putAll(mapA);
運行此代碼後,變量mapB引用的Map,將包含代碼示例開頭插入mapA的兩個鍵值條目。
條目的複製只有一種方式。 調用mapB.putAll(mapA)只會將mapA中的條目複製到mapB中,而不是從mapB複製到mapA中。 要以其他方式複製條目,您必須執行代碼mapA.putAll(mapB)。
2.4.訪問Map中元素
要訪問存儲在Java Map中的特定元素,可以調用其get()方法,並將該元素的鍵作為參數傳遞。 以下是訪問存儲在Java Map中的值的示例:
String element1 = (String) mapA.get("key1");
請注意,get()方法返回一個Java對象,因此我們必須將其強制轉換為String(因為我們知道該值是一個String)。 稍後在本Java Map教程中,您將看到如何使用Java泛型Map,以便它知道它包含哪些特定的鍵和值類型。 這使得類型轉換變得不必要,並且使得錯誤的值偶然地插入Map中變得更加困難。
1)檢查Map中是否包含某鍵
可以使用containsKey()方法檢查Java Map是否包含特定鍵。就像如下所示:
boolean hasKey = mapA.containsKey("123");
運行此代碼後,如果先前使用字符串鍵"123"插入了鍵值對,則hasKey變量的值為true;如果未插入此類鍵值對,則為false。
2)檢查Map中是否包含某值
Java Map接口還有一個方法,使您可以檢查Map是否包含特定值。 該方法稱為containsValue()。 以下是調用containsValue()的方式:
boolean hasValue = mapA.containsValue("value 1");
執行此代碼後,如果鍵值對用字符串值"value 1"插入,則hasValue變量將包含值true,否則返回false。
2.5.迭代Map的鍵
有幾種方法可以迭代存儲在Java Map中的鍵。 迭代鍵最常用的方法是迭代器:
Iterator
以下各節將介紹這兩種方法。
1)使用鍵迭代器
您可以通過其keySet()方法迭代Map的所有鍵。 以下是迭代Map的鍵的方式:
Iterator iterator = mapA.keySet().iterator();
while(iterator.hasNext(){
Object key = iterator.next();
Object value = mapA.get(key);
}
如您所見,鍵Iterator逐個返回存儲在Map中的每個鍵(每次調用next()獲得一個鍵)。 獲得鍵後,可以使用Map的get()方法獲取為該鍵存儲的元素。
2)使用鍵ForEach循環
從Java 5開始,您還可以使用for-each循環來迭代存儲在Java Map中的鍵。看起來這樣:
for(Object key : mapA.keySet()) {
Object value = mapA.get(key);
}
上述代碼的效果與上一節(鍵迭代器)中顯示的代碼非常相似。
2.6.迭代Map的值
也可以只迭代存儲在Java Map中的值。通過values()方法獲取存儲在Map中的一組值。可用兩種方式迭代Set中的值:
● 使用迭代器
● 使用for-each循環
下面介紹了這兩個方法。
1)使用值迭代器
迭代存儲在Map中的所有值的第一種方法,是從值Set獲取值Iterator實例,並迭代它。以下是使用值Iterator迭代存儲在Map中的值的方法:
Iterator iterator = map.values().iterator();
while(iterator.hasNext()) {
Object nextValue iterator.next();
}
由於Set是無序的,因此無法保證迭代值的順序。
2)使用For-Each循環值
迭代存儲在Map中值的第二種方法是通過Java for-each循環。 下面是如何使用for-each循環在代碼中迭代Map的值:
for(Object value : mapA.values()){
System.out.println(value);
}
此示例將打印出mapA變量中存儲的所有值。
2.7.迭代Map的條目
也可以迭代Java Map的所有條目。條目我指的是鍵值對。 條目包含該條目的鍵和值。 之前我們只迭代了鍵或值,但通過迭代條目,我們能同時迭代二者(鍵值)。
與鍵和值一樣,有兩種方法可以迭代Map的條目:
● 使用Entry Iterator
● 使用for-each循環
下面介紹這兩個選項。
1)使用條目迭代器
迭代Java Map條目的第一種方法是通過從條目Set獲得的條目迭代器。 這是迭代Map條目的示例:
Set entries = map.entrySet();
Iterator iterator = entries.iterator();
while(iterator.hasNext()) {
Map.Entry entry = (Map.Entry) iterator.next();
Object key = entry.getKey();
Object value = entry.getValue();
}
注意如何從每個Map.Entry實例獲取鍵和值。
請記住,使用Java泛型的Map可使上面的代碼更好一些,如本教程後面會講到。
2)使用ForEach循環迭代條目
迭代Map條目的第二種方法是使用for-each循環。 下面是使用for-each循環迭代JMap條目的示例:
for(Object entryObj : map.entrySet()){
Map.Entry entry = (Map.Entry) entryObj;
Object key = entry.getKey();
Object value = entry.getValue();
}
請注意,使用泛型Map可以使這個示例更漂亮。 稍後將在教程中解釋泛型Map實例。
2.8.從Map中刪除條目
1)刪除單個條目
可以通過調用remove(Object key)方法刪除條目, 這樣就刪除與鍵匹配的(鍵,值)對。 以下是刪除Map中給定鍵的條目的示例:
mapA.remove("key1");
執行此指令後,mapA引用的Map將不再包含鍵key1的條目(鍵值對)。
2)刪除所有條目
可以使用clear()方法刪除Java Map中的所有條目。如下操作:
mapA.clear();
2.9.替換Map中的條目
可以使用replace()方法替換Java Map中的元素。 如果已存在映射到鍵的現值,則replace()方法將僅插入新值。 如果沒有現有值映射到給定鍵,則不會插入任何值。 這與put()的工作方式不同,無論如何都會插入值。
下面是使用 Map的replace()方法將一個值替換為另一個值的示例:
Map map = new HashMap();
map.replace("key", "new value"); //啥也不替換,沒有對鍵"key"的映射
map.put("key", "value"); //現在map包含了一個"key"條目
map.replace("key", "newer value"); //條目被替換
運行此代碼後,Map實例將包含字符串鍵key的字符串新值"newer value"。
2.10.讀取Map中條目的數量
您可以使用size()方法讀取Java Map中的條目數。 Java Map中的條目數也稱為Map大小——方法名size()的顧名思義。 下面是使用size()方法讀取Map中條目數的示例:
int entryCount = mapA.size();
2.11.檢查Map是否為空
Java Map接口有一個特殊的方法來檢查Map是否為空。 此方法稱為isEmpty(),它返回true或false。 如果Map實例包含1個或多個條目,則isEmpty()方法將返回false。 如果Map包含0個條目,則isEmpty()將返回true。
3.泛型Map
默認情況下,您可以將任何Object放入Map中,但是從Java 5開始,Java 泛型可以限制用於Map中的鍵和值的對象類型。示例如下:
Mapmap = new HashMap ();
此Map的鍵現在只能接受String對象,以及值的MyObject實例。 然後,您可以訪問並迭代鍵和值,而無需強制轉換它們。看起來像這樣:
for(MyObject anObject : map.values()){
//用anObject做點什麼...
}
……
for(String key : map.keySet()){
MyObject value = map.get(key);
//針對value做點什麼
}
注意,這裡的鍵值可以是任意的有效類型,同時注意泛型聲明的格式。
關於泛型的更多內容,另外再行講解,這裡不再多講了。
4.Map中的函數操作
Map接口在Java 8中添加了一些函數操作。這些函數操作,使得可以更實用的方式與Map交互。 例如,將Java Lambda 表達式作為參數傳遞給這些函數風格的方法。關於Java函數編,另行講解:
這些函數操作方法如下:
· compute()
· computeIfAbsent()
· computeIfPresent()
· merge()
以下各節將更詳細地描述這些功能的每一種方法。
1)compute()方法
Map的compute()方法將鍵對象和lambda表達式作為參數。 lambda表達式必須實現java.util.function.BiFunction接口。 以下是調用Map的compute()方法的示例:
map.compute("123", (key, value) -> value == null ? null : value.toString().toUpperCase());
compute()方法將在內部調用lambda表達式,將key對象及Map中存儲在該鍵對象下的任何值作為參數傳遞lambda表達式的。
無論lambda表達式返回什麼值,都會被存儲在該鍵下而替換原當前存儲的值。 如果lambda表達式返回null,則刪除該條目。Map中不會存在key->null的映射。
在上面的示例中,您可以看到lambda表達式在調用toString().toUpperCase()之前檢查映射到給定鍵的值是否為null。
如lambda表達式拋出異常,該條目也被刪除。
2)computeIfAbsent()方法
Map的computeIfAbsent()方法與compute()方法的工作方式類似,但只有在給定鍵的條目不存在的情況下才會調用lambda表達式。
lambda表達式返回的值將插入到Map中。 如果返回null,則不插入任何條目。
如果lambda表達式拋出異常,則也不會插入任何條目。
以下是調用Map的computeIfAbsent()方法的示例:
map.computeIfAbsent("123", (key) -> "abc");
這個例子實際上只返回一個常量值——字符串123。 但是,lambda表達式可以以任何方式計算值 ,例如 從另一個對象中提取值,或者從其他值中連接等。
3) computeIfPresent()方法
Map的computeIfPresent()方法與computeIfAbsent()相反。 如果Map中該鍵已存在一個條目,它只調用作為參數傳遞給它的lambda表達式。 以下是調用computeIfPresent()方法的示例:
map.computeIfPresent("123", (key, value) -> value == null ? null : value.toString().toUpperCase());
lambda表達式返回的值將插入到Map實例中。 如果lambda表達式返回null,則刪除給定鍵的條目。
如果lambda表達式拋出異常,則重新拋出異常,並且給定鍵的當前條目保持不變。
4)merge()方法
Map的merge()方法接受一個鍵、一個值和一個實現BiFunction接口的lambda表達式作為參數。
如果Map沒有指定鍵的條目,或者鍵的值為null,則作為參數傳遞給merge()方法的值被插入到給定的鍵。
但是,如果現有值已映射到給定鍵,則調用作為參數傳遞的lambda表達式。 因此,lambda表達式有機會將現有值與新值合併。 然後將lambda表達式返回的值插入到給定鍵的Map中。 如果lambda表達式返回null,則刪除給定鍵的條目。
以下是調用Map merge()方法的示例:
map.merge("123", "XYZ", (oldValue, newValue) -> newValue + "-abc");
如果沒有值映射到鍵(123),或者將null映射到鍵,則此示例將值XYZ插入Map。 如果已將非空值映射到鍵,則調用lambda表達式。 lambda表達式返回新值( XYZ ) + 值-abc,表示XYZ-abc。
如果lambda表達式拋出異常,則方法重新拋出異常,並保持給定鍵的當前映射不變。
您可以使用Map執行更多操作,但是您需要查看JavaDoc以獲取更多詳細信息。本文主要關注兩個最常見的操作:添加/刪除元素,以及迭代鍵和值,另外,還有新的函數式的操作。
寫得累死我了……
好了,本文就寫這麼多了,希望有助於你更好的探索Java中的Map。
閱讀更多 牛旦教育IT課堂 的文章