进阶实践:搞定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。



分享到:


相關文章: