進階實踐:搞定Java Map,先從這切入,兼解Map函數式應用

搞定Java Map,先從這進入簡介Map函數式應用

Java Map接口,即java.util.Map,表示鍵和值之間的映射。 更具體地說,Java Map可以存儲鍵和值對。 每個鍵都鏈接到一個特定的值。 存儲在Map中後,可以僅使用鍵查找值。

Java Map接口不是Collection接口的子類型。 因此它的行為與其他集合類型略有不同。

進階實踐:搞定Java Map,先從這切入,兼解Map函數式應用

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中的鍵和值的對象類型。示例如下:

Map map = 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。



分享到:


相關文章: