Java映射Map

时间:2020-01-09 10:35:48  来源:igfitidea点击:

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还映射一个键和一个值。此外,它保证了键或者值的迭代顺序,即键或者值的排序顺序。请查看JavaMapJavaDoc了解更多详细信息。

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()的工作方式不同,后者无论如何总是插入值。

这是一个使用JavaMapreplace()方法将一个值替换为另一个值的示例:

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中的条目数也称为Mapsize,因此称为方法名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接口。这是调用JavaMapcompute()`方法的示例:

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",则将删除给定键的条目。

这是一个调用Mapmerge()方法的例子:

map.merge("123", "XYZ", 
    (oldValue, newValue) -> newValue + "-abc");

如果没有值映射到键(123),或者如果将" null"映射到键,则本示例将值" XYZ"插入" Map"。如果非null值已经映射到键,则调用lambda表达式。 lambda表达式返回新值(XYZ)+值-abc,表示XYZ-abc。

如果lambda表达式引发异常,则重新引发该异常,并且给定键的当前映射保持不变。