Java映射Map
Java Map接口java.util.Map表示键和值之间的映射。更具体地说,Java Map可以存储键和值对。每个键都链接到一个特定的值。一旦存储在"地图"中,我们以后就可以只使用键来查找值。
JavaMap
接口不是Collection
接口的子类型。因此,它的行为与其余集合类型略有不同。
Java Map实现
由于Map是接口,因此我们需要实例化Map接口的具体实现才能使用它。 Java Collections 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映射一个键和一个值。它不保证内部存储在映射中的元素的任何顺序。
TreeMap还映射一个键和一个值。此外,它保证了键或者值的迭代顺序,即键或者值的排序顺序。请查看JavaMap
JavaDoc了解更多详细信息。
HashMap实现通常是两个Map实现中最快的,因此,每当不需要对Map中的元素进行排序时,都可以使用HashMap。否则,请使用TreeMap。
建立MAP
要创建Java Map,我们必须创建实现Java Map接口的类之一的实例。以下是一些有关如何创建Map实例的示例:
Map mapA = new HashMap(); Map mapB = new TreeMap();
通用Java映射
默认情况下,我们可以将任何"对象"放入"地图"中,但是从Java 5开始,Java泛型可以限制可用于"地图"中的键和值的对象类型。这是一个例子:
Map<String, MyObject> map = new HashMap<String, MyObject>();
现在,这个"地图"只能接受"字符串"对象作为键,而" MyObject"实例作为值。然后,我们可以访问和迭代键和值,而无需强制转换它们。外观如下:
for(MyObject anObject : map.values()){ //do someting with anObject... } for(String key : map.keySet()){ MyObject value = map.get(key); //do something to value }
如果我们知道要存储在Map中的对象的类型,那么在声明和创建Java Map时始终指定通用类型被认为是一种好习惯。通用类型可避免插入错误的对象,并使人们在阅读代码时更容易理解Map包含的对象类型。在本教程的其余部分中,我将在所有有意义的Map示例中使用泛型类型。
有关Java泛型的更多信息,请参见Java泛型教程。
将元素插入Java映射
要将元素添加到Map中,请调用其put()方法。这里有一些例子:
Map<String, String> map = new HashMap<>(); map.put("key1", "element 1"); map.put("key2", "element 2"); map.put("key3", "element 3");
三个put()
调用将一个字符串值映射到一个字符串键。然后,我们可以使用该键获取值,正如我们将在下一部分中看到的那样。
只能插入对象
Java映射中只能将Java对象用作键和值。如果我们将原始值(例如int,double等)作为键或者值传递给" Map",则在将原始值作为参数传递之前,它们会自动装箱。这是传递到put()
方法的自动装箱原始参数的示例:
map.put("key", 123);
在上面的示例中,传递给put()
方法的值是原始的int
。但是Java会将其自动装箱到Integer实例中,因为put()方法需要一个Oject实例作为键和值。如果我们将原语作为key放入put()方法,也会发生自动装箱。
后续插入具有相同的密钥
给定的密钥只能在Java" Map"中出现一次。这意味着,每个键只能有一个键+值对同时存在于"映射"中。换句话说,对于键" key1",只能在同一" Map"实例中存储一个值。当然,我们可以将相同键的值存储在不同的" Map"实例中。
如果我们使用同一个键多次调用put()
,则该键传递给put()
的最新值将覆盖该键在Map中已经存储的内容。换句话说,最新值将替换给定键的现有值。
null键
令人惊讶的是,我们可以在Java Map中使用值" null"作为键。这是在Java Map中使用null
键的示例:
Map map = new HashMap(); map.put(null, "value for null key");
要获取由null键存储的值,我们可以调用get()方法,其中null为参数值。这是获取Java Map的null
键值的示例:
Map<String, String> map = new HashMap<>(); String value = map.get(null);
null值
存储在"MAP"中的键+值对的值允许为空,因此这是有效的:
map.put("D", null);
请记住,稍后使用该键调用get()
时,我们将得到一个空值,因此它将返回空值:
Object value = map.get("D");
如果早先为此键插入了" null"值,则在执行完此代码后," value"变量的值将为" null"。
从另一个地图插入所有元素
JavaMap
接口有一个名为putAll()
的方法,它可以将所有键+值对(条目)从另一个Map
实例复制到自身。在集合论中,这也称为两个" Map"实例的并集。
这是一个通过putAll()
将所有条目从一个JavaMap
复制到另一个Java的示例:
Map<String, String> mapA = new HashMap<>(); mapA.put("key1", "value1"); mapA.put("key2", "value2"); Map<String, String> mapB = new HashMap<>(); mapB.putAll(mapA);
运行此代码后,变量" mapB"引用的" Map"将包含在代码示例开头插入" mapA"中的两个键和值条目。
条目的复制只有一种方式。调用mapB.putAll(mapA)
只会将条目从mapA
复制到mapB
,而不是从mapB
复制到mapA
。要以其他方式复制条目,则必须执行代码mapA.putAll(mapB)
。
从Java映射获取元素
要获取存储在Java Map中的特定元素,请调用其get()方法,并将该元素的键作为参数传递。这是获取存储在Java Map中的值的示例:
Map map = new HashMap(); map.put("key1", "value 1"); String element1 = (String) map.get("key1");
注意,get()方法返回一个Java Object,因此我们必须将其强制转换为String(因为我们知道该值为String)。在本Java Map教程的稍后部分,我们将看到如何使用Java泛型键入Map
,从而知道它包含哪些特定的键和值类型。这使得类型转换变得不必要,并且使得更难以将错误的值意外地插入到" Map"中。
如果我们为Map的键和值指定了通用类型,则无需强制转换get()方法返回的对象。外观如下:
Map<String, String> map = new HashMap<>(); map.put("key1", "value 1"); String element1 = map.get("key1");
获取或者默认值
Java Map接口具有一个getOrDefault()方法,如果给定键在Map中没有存储任何值,则该方法可以返回我们提供的默认值。这是一个从JavaMap
获取具有备份默认值的值的示例:
Map<String, String> map = new HashMap<>(); map.put("A", "1"); map.put("B", "2"); map.put("C", "3"); String value = map.getOrDefault("E", "default value");
这个例子创建了一个Map,并使用键A,B和C其中存储了三个值。然后该示例调用Map的getOrDefault()方法,将字符串E以及默认值传递给String默认值。由于Map不包含键E所存储的任何对象,因此将返回给定的默认值,该默认值是作为最后一个参数传递给getOrDefault()方法的字符串" default value"。
检查地图是否包含密钥
我们可以使用containsKey()方法检查Java Map是否包含特定键。看起来是这样的:
boolean hasKey = map.containsKey("123");
运行此代码后,如果早先使用字符串键" 123"插入了键+值对,则" hasKey"变量的值将为" true",而如果未插入这样的键+值对,则" false"值为false。
检查地图是否包含值
JavaMap
接口也有一种方法,使我们可以检查Map
是否包含某个值。该方法称为" containsValue()"。这是调用containsValue()
的样子:
boolean hasValue = map.containsValue("value 1");
执行此代码后,如果插入的键+值对的字符串值是"值1",则" hasValue"变量将包含值" true",否则为false。
迭代Java映射的键
有几种方法可以迭代存储在Java Map中的键。迭代密钥最常用的方法是:
- 通过键
Iterator
- 通过for-each循环
- 通过`流
以下各节将介绍所有方法。
使用密钥迭代器
我们可以通过Java Map的keySet()方法来迭代其所有键。这是迭代JavaMap
的键的样子:
Iterator iterator = map.keySet().iterator(); while(iterator.hasNext(){ Object key = iterator.next(); Object value = map.get(key); }
如我们所见,键Iterator一次又一次地返回存储在Java Map中的每个键(每次调用next()时一个键)。有了密钥后,就可以使用Map`get()方法获取为该密钥存储的元素。
在上面的示例中,Iterator的next()方法返回一个Object,而get()方法也返回了Object。在为Map指定通用类型的情况下,这些方法将分别返回键和值对象的类型。外观如下:
Map<String, String> map = new HashMap<>(); Iterator<String> iterator = map.keySet().iterator(); while(iterator.hasNext(){ String key = iterator.next(); String value = map.get(key); }
请注意,现在还如何为从map.keySet()。iterator()获得的Iterator指定通用类型。
使用密钥每次循环
在Java 5中,我们还可以使用for-each循环来迭代存储在Java" Map"中的键。看起来是这样的:
for(Object key : map.keySet()) { Object value = map.get(key); }
上面的代码的效果与上一节中显示的代码非常相似。
如果为Java Map指定了通用类型,则可以在for-each循环内使用该类型。看起来是这样的:
Map<String, String> map = new HashMap<>(); for(String key : map.keySet()) { String value = map.get(key); }
使用密钥流
在Java 8中,我们可以使用Java Stream来迭代Java Map的键。 Stream接口是Java 8中添加的Java Stream API的一部分。首先从Map中获取键Set,然后从中获得Stream。这是一个通过Stream迭代Java Map的键的示例:
Map<String, String> map = new HashMap<>(); map.put("one", "first"); map.put("two", "second"); map.put("three", "third"); Stream<String> stream = map.keySet().stream(); stream.forEach((value) -> { System.out.println(value); });
迭代Java映射的值
也可以只迭代存储在Java Map中的值。我们可以通过values()方法获取存储在Map中的值的Collection。我们可以通过以下方式迭代"集合"中的值:
- 使用迭代器
- 使用for-each循环
- 使用价值流
以下各节将介绍所有这些选项。
使用值迭代器
迭代存储在Java Map中的所有值的第一种方法是从值Set中获取值Iterator实例,然后对其进行迭代。这是使用值Iterator
迭代存储在JavaMap
中的值的方法:
Map<String, String> map = new HashMap<>(); Iterator<String> iterator = map.values().iterator(); while(iterator.hasNext()) { String nextValue iterator.next(); }
由于Set是无序的,因此对于值集中的值的迭代顺序,我们没有任何保证。但是,如果我们使用的是TreeSet,则仍可以控制此顺序。
使用价值每次循环
迭代Java Map中的值存储的第二种方法是通过Java for-each循环。这是使用for-each循环在代码中迭代JavaMap
的值的方式:
Map<String, String> map = new HashMap<>(); for(String value : map.values()){ System.out.println(value); }
这个例子将打印出所有存储在mapA变量Map中的值。
使用价值流
迭代存储在Java Map中的值的第三种方法是使用Java Stream API使用值Stream。首先从Map中获取Set值,然后从Set中获取Stream。这是一个通过值Stream来迭代Java Map的值的示例:
Map<String, String> map = new HashMap<>(); map.put("one", "first"); map.put("two", "second"); map.put("three", "third"); Stream<String> stream = map.values().stream(); stream.forEach((value) -> { System.out.println(value); });
迭代Java映射的条目
也可以迭代Java Map的所有条目。输入是指键+值对。条目包含该条目的键和值。之前我们只迭代了键或者值,但是通过迭代条目,我们同时迭代了两个。
像键和值一样,有两种方法可以迭代" Map"的条目:
- 使用入口迭代器
- 使用for-each循环
这两个选项将在以下各节中进行说明。
使用入口迭代器
迭代Java Map条目的第一种方法是通过从Set条目获得的Iterator条目。这是一个迭代JavaMap
条目的示例:
Set<Map.Entry<String, String>> entries = map.entrySet(); Iterator<Map.Entry<String, String>> iterator = entries.iterator(); while(iterator.hasNext()) { Map.Entry<String, String> entry = iterator.next(); String key = entry.getKey(); String value = entry.getValue(); }
注意如何从每个Map.Entry
实例中获取键和值。
请记住,使用本Java泛型键入的Map可以使上面的代码更好一些,如本教程的后面所示。
使用每个入口循环
迭代Java Map条目的第二种方法是使用for-each循环。这是一个使用for-each循环迭代JavaMap
条目的示例:
for(Map.Entry<String, String> entry : map.entrySet()){ String key = entry.getKey(); String value = entry.getValue(); }
注意,使用通用的Map也可以使这个例子更漂亮。通用Map实例将在本Java Map教程的稍后部分进行解释。
从Java映射中删除条目
我们可以通过调用remove(Object key)
方法来删除条目。因此,我们将删除与该键匹配的(键,值)对。这是在JavaMap
中删除给定键的条目的示例:
map.remove("key1");
执行完该指令后,由mapA引用的Map将不再包含key1的条目(键+值对)。
删除所有条目
我们可以使用clear()方法删除Java Map中的所有条目。看起来是这样的:
map.clear();
替换Java映射中的条目
使用replace()
方法可以替换JavaMap
中的一个元素。如果已经存在映射到键的现有值,则replace()
方法将仅插入新值。如果没有现有值映射到给定键,则不会插入任何值。这与put()的工作方式不同,后者无论如何总是插入值。
这是一个使用JavaMap
replace()方法将一个值替换为另一个值的示例:
Map<String, String> map = new HashMap<>(); map.replace("key", "val2"); //no "key" entry, no replace map.put("key", "val1"); //now contains "key" entry map.replace("key", "val2"); //now "key" entry replaced
运行此代码后," Map"实例将包含字符串键" key"的字符串值" newer value"。
阅读地图中的条目数
我们可以使用size()方法读取Java Map中的条目数。 JavaMap
中的条目数也称为Map
size,因此称为方法名size()
。这是一个使用size()
方法读取Map中条目数的示例:
int entryCount = map.size();
检查Java Map是否为空
JavaMap
接口具有一种特殊的方法来检查Map
是否为空。该方法称为" isEmpty()",它返回" true"或者" false"。如果Map实例包含1个或者多个条目,则isEmpty()方法将返回false。如果Map包含0个条目,则isEmpty()将返回true。
Java Map中的功能操作
JavaMap
界面从Java 8中添加了一些功能性操作。这些功能性操作使以更实用的风格与" Map"进行交互成为可能。例如,我们将Java Lambda表达式作为参数传递给这些功能样式方法。
功能操作方法有:
- compute()
- computeIfAbsent()
- computeIfPresent()
- merge()
在以下各节中将更详细地描述这些功能方法中的每一个。
compute()
Mapcompute()
方法将一个键对象和一个lambda表达式作为参数。 lambda表达式必须实现java.util.function.BiFunction
接口。这是调用JavaMap
compute()`方法的示例:
map.compute("123", (key, value) -> value == null ? null : value.toString().toUpperCase());
compute()方法将在内部调用lambda表达式,并传递键对象和该键对象在Map中存储的任何值作为lambda表达式的参数。
将存储lambda表达式返回的任何值,而不是该键的当前存储值。如果lambda表达式返回" null",则该条目将被删除。 Map
中不会存储键->null
映射。
在上面的示例中,我们可以看到lambda表达式在调用toString()。toUpperCase()之前先检查映射到给定键的值是否为null。
如果lambda表达式引发异常,则该条目也将被删除。
computeIfAbsent()
MapcomputeIfAbsent()
方法的工作方式与compute()
类似,但是只有在给定键没有条目的情况下才调用lambda表达式。
lambda表达式返回的值被插入到Map中。如果返回null
,则不插入任何条目。
如果lambda表达式引发异常,则也不会插入任何条目。
这是一个调用Map的computeIfAbsent()方法的示例:
map.computeIfAbsent("123", (key) -> "abc");
这个例子实际上只是返回一个常量值字符串123. 但是,lambda表达式可能已经以任何需要的方式计算了该值,例如从另一个对象中提取值,或者从其他值中将其连接起来,等等。
computeIfPresent()
MapcomputeIfPresent()
方法的工作方式与computeIfAbsent()相反。如果该键的"映射"中已存在条目,则仅调用作为参数传递给它的lambda表达式。这是一个调用computeIfPresent()
方法的示例:
map.computeIfPresent("123", (key, value) -> value == null ? null : value.toString().toUpperCase());
lambda表达式返回的值将插入到Map实例中。如果lambda表达式返回" null",则将删除给定键的条目。
如果lambda表达式引发异常,则重新引发该异常,并且给定键的当前条目保持不变。
merge()
Mapmerge()
方法采用一个键,一个值和一个实现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"。如果非null值已经映射到键,则调用lambda表达式。 lambda表达式返回新值(XYZ)+值-abc,表示XYZ-abc。
如果lambda表达式引发异常,则重新引发该异常,并且给定键的当前映射保持不变。