Java™ 教程(Map接口)

Map接口

Map是將鍵映射到值的對象,map不能包含重複的鍵:每一個鍵最多能夠映射一個值,它模擬數學函數抽象。Map接口包括基本操做的方法(如putgetremovecontainsKeycontainsValuesizeempty),批量操做(如putAllclear)和集合視圖(如keySetentrySetvalues)。html

Java平臺包含三個通用Map實現:HashMapTreeMapLinkedHashMap,它們的行爲和性能徹底相似於HashSetTreeSetLinkedHashSet,如Set接口部分所述。java

本頁的其他部分詳細討論了Map接口,但首先,這裏有一些使用JDK 8聚合操做收集到Map的示例,對現實世界對象進行建模是面向對象編程中的常見任務,所以能夠合理地認爲某些程序可能會按部門對員工進行分組:react

// Group employees by department
Map<Department, List<Employee>> byDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));

或者按部門計算全部工資的總和:git

// Compute sum of salaries by department
Map<Department, Integer> totalByDept = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.summingInt(Employee::getSalary)));

或者經過成績及格或成績不及格分組學生:github

// Partition students into passing and failing
Map<Boolean, List<Student>> passingFailing = students.stream()
.collect(Collectors.partitioningBy(s -> s.getGrade()>= PASS_THRESHOLD));

你還能夠按城市分組:編程

// Classify Person objects by city
Map<String, List<Person>> peopleByCity
         = personStream.collect(Collectors.groupingBy(Person::getCity));

或者甚至級聯兩個收集器按州和城市對人進行分類:segmentfault

// Cascade Collectors 
Map<String, Map<String, List<Person>>> peopleByStateAndCity
  = personStream.collect(Collectors.groupingBy(Person::getState,
  Collectors.groupingBy(Person::getCity)))

一樣,這些只是如何使用新JDK 8 API的幾個示例,有關lambda表達式和聚合操做的深刻介紹,請參閱標題爲聚合操做的課程。api

Map接口基本操做

MapputgetcontainsKeycontainsValuesizeisEmpty)的基本操做與Hashtable中的對應操做徹底相同,如下程序生成其參數列表中找到的單詞的頻率表,頻率表將每一個單詞映射到它在參數列表中出現的次數。安全

import java.util.*;

public class Freq {
    public static void main(String[] args) {
        Map<String, Integer> m = new HashMap<String, Integer>();

        // Initialize frequency table from command line
        for (String a : args) {
            Integer freq = m.get(a);
            m.put(a, (freq == null) ? 1 : freq + 1);
        }

        System.out.println(m.size() + " distinct words:");
        System.out.println(m);
    }
}

關於這個程序惟一棘手的問題是put語句的第二個參數,該參數是一個條件表達式,若是單詞以前從未出現過,則其頻率設置爲1,若是單詞已經出現,則其頻率設置爲當前值加1,嘗試使用如下命令運行此程序:oracle

java Freq if it is to be it is up to me to delegate

該程序產生如下輸出。

8 distinct words:
{to=3, delegate=1, be=1, it=2, up=1, if=1, me=1, is=2}

假設你但願按字母順序查看頻率表,你所要作的就是將Map的實現類型從HashMap更改成TreeMap,進行這種更改會致使程序從同一命令行生成如下輸出。

8 distinct words:
{be=1, delegate=1, if=1, is=2, it=2, me=1, to=3, up=1}

相似地,你能夠經過將map的實現類型更改成LinkedHashMap,使程序按照單詞首次出如今命令行上的順序打印頻率表,這樣作會產生如下輸出。

8 distinct words:
{if=1, it=2, is=2, to=3, be=1, up=1, me=1, delegate=1}

這種靈活性提供了基於接口的框架功能的有力說明。

SetList接口同樣,Map強化了對equalshashCode方法的要求,所以能夠比較兩個Map對象的邏輯相等性,而不考慮它們的實現類型,若是兩個Map實例表示相同的鍵值映射,則它們是相等的。

按照慣例,全部通用Map實現都提供構造函數,這些構造函數接受Map對象並初始化新Map以包含指定Map中的全部鍵值映射。這個標準的Map轉換構造函數徹底相似於標準的Collection構造函數:它容許調用者建立一個所需實現類型的Map,該Map最初包含另外一個Map中的全部映射,而無論其餘Map的實現類型如何。例如,假設你有一個名爲mMap,如下單行建立一個新的HashMap,最初包含與m相同的全部鍵值映射。

Map<K, V> copy = new HashMap<K, V>(m);

Map接口批量操做

clear的操做徹底符合你的想法:它從Map中刪除全部映射。putAll操做是Collection接口的addAll操做的Map模擬,除了明顯使用將一個Map轉儲到另外一個Map以外,它還有第二個更微妙的用途,假設Map用於表示屬性—值對的集合,putAll操做與Map轉換構造函數結合使用,提供了一種使用默認值實現屬性映射建立的簡潔方法。如下是演示此技術的靜態工廠方法。

static <K, V> Map<K, V> newAttributeMap(Map<K, V>defaults, Map<K, V> overrides) {
    Map<K, V> result = new HashMap<K, V>(defaults);
    result.putAll(overrides);
    return result;
}

集合視圖

Collection視圖方法容許以這三種方式將Map視爲Collection

  • keySetMap中包含鍵的Set
  • valuesMap中包含值的Collection,此Collection不是Set,由於多個鍵能夠映射到相同的值。
  • entrySetMap中包含的鍵值對的SetMap接口提供了一個名爲Map.Entry的小型嵌套接口,該接口是此Set中元素的類型。

Collection視圖提供迭代Map的惟一方法,此示例說明了使用for-each構造迭代Map中的鍵的標準語法:

for (KeyType key : m.keySet())
    System.out.println(key);

使用迭代器:

// Filter a map based on some 
// property of its keys.
for (Iterator<Type> it = m.keySet().iterator(); it.hasNext(); )
    if (it.next().isBogus())
        it.remove();

迭代值的語法是相似的,如下是迭代鍵值對的語法。

for (Map.Entry<KeyType, ValType> e : m.entrySet())
    System.out.println(e.getKey() + ": " + e.getValue());

起初,許多人擔憂這些語法可能會很慢,由於每次調用Collection視圖操做時Map都必須建立一個新的Collection實例,放鬆:每次要求給定的Collection視圖時,Map都沒有理由不能老是返回相同的對象,這正是java.util中全部Map實現的功能。

對於全部這三個Collection視圖,調用Iteratorremove操做將從支持Map中刪除相關條目,假設支持Map一開始就支持元素刪除,這由前面的過濾語法說明。

使用entrySet視圖,還能夠經過在迭代期間調用Map.EntrysetValue方法來更改與鍵關聯的值(一樣,假設Map一開始就支持值修改)。請注意,這些是在迭代期間修改Map的惟一安全方法,若是在迭代進行過程當中以任何其餘方式修改基礎Map,則行爲是未指定的。

Collection視圖支持以多種形式刪除元素 — removeremoveAllretainAllclear操做,以及Iterator.remove操做(一樣,這假設支持Map支持元素刪除)。

Collection視圖在任何狀況下都不支持元素添加,對於keySetvalues視圖沒有任何意義,而且對於entrySet視圖沒有必要,由於支持MapputputAll方法提供相同的功能。

Collection視圖的花哨用途:Map代數

應用於Collection視圖時,批量操做(containsAllremoveAllretainAll)是使人驚訝的強大工具。對於初學者,假設你想知道一個Map是不是另外一個Map的子圖 — 也就是說,第一個Map是否包含第二個Map中的全部鍵值映射,如下語法能夠解決這個問題。

if (m1.entrySet().containsAll(m2.entrySet())) {
    ...
}

沿着相似的路線,假設你想知道兩個Map對象是否包含全部相同鍵的映射。

if (m1.keySet().equals(m2.keySet())) {
    ...
}

假設你有一個表示屬性—值對集合的Map,以及兩個表示所需屬性和容許屬性的Set(容許的屬性包括必需的屬性),如下代碼段肯定屬性映射是否符合這些約束,若是不符合則打印詳細的錯誤消息。

static <K, V> boolean validate(Map<K, V> attrMap, Set<K> requiredAttrs, Set<K>permittedAttrs) {
    boolean valid = true;
    Set<K> attrs = attrMap.keySet();

    if (! attrs.containsAll(requiredAttrs)) {
        Set<K> missing = new HashSet<K>(requiredAttrs);
        missing.removeAll(attrs);
        System.out.println("Missing attributes: " + missing);
        valid = false;
    }
    if (! permittedAttrs.containsAll(attrs)) {
        Set<K> illegal = new HashSet<K>(attrs);
        illegal.removeAll(permittedAttrs);
        System.out.println("Illegal attributes: " + illegal);
        valid = false;
    }
    return valid;
}

假設你想知道兩個Map對象共有的全部鍵。

Set<KeyType>commonKeys = new HashSet<KeyType>(m1.keySet());
commonKeys.retainAll(m2.keySet());

相似的語法能夠爲你提供共同的值。

到目前爲止提出的全部語法都是非破壞性的,也就是說,它們不會修改支持Map,這裏有一些,假設你要刪除一個Map與另外一個Map共有的全部鍵值對。

m1.entrySet().removeAll(m2.entrySet());

假設你要從一個Map中刪除在另外一個Map中具備映射的全部鍵。

m1.keySet().removeAll(m2.keySet());

在同一個批量操做中開始混合鍵和值時會發生什麼?假設你有一個Mapmanagers,將公司中的每一個員工映射到員工的經理,咱們會故意模糊鍵和值對象的類型,不要緊,只要它們是相同的,如今假設你想知道全部「我的貢獻者」(或非管理者)是誰,如下代碼段將準確告訴你你想要了解的內容。

Set<Employee> individualContributors = new HashSet<Employee>(managers.keySet());
individualContributors.removeAll(managers.values());

假設你要解僱全部直接向某位經理Simon報告的員工。

Employee simon = ... ;
managers.values().removeAll(Collections.singleton(simon));

請注意,這個語法是使用Collections.singleton,這是一個靜態工廠方法,它返回一個帶有指定元素的不可變Set

一旦你完成了這項工做,你可能會有一羣員工,他們的經理再也不爲公司工做(若是任何Simon的直接報告自己就是經理),如下代碼將告訴你哪些員工擁有再也不爲公司工做的經理。

Map<Employee, Employee> m = new HashMap<Employee, Employee>(managers);
m.values().removeAll(managers.keySet());
Set<Employee> slackers = m.keySet();

這個例子有點棘手,首先,它建立Map的臨時副本,並從臨時副本中刪除其(manager)值是原始Map中的鍵的全部條目,請記住,原始Map爲每一個員工都有一個條目。所以,臨時Map中的其他條目包括來自原始Map的其(經理)值再也不是僱員的全部條目,所以,臨時副本中的鍵剛好表明了咱們正在尋找的員工。

多重映射

多重映射就像Map,但它能夠將每一個鍵映射到多個值,Java集合框架不包含多重映射的接口,由於它們並不經常使用。使用Map值爲List實例做爲多重映射的Map是一件至關簡單的事情。下一個代碼示例演示了此技術,該示例讀取每行包含一個單詞(所有小寫)的單詞列表,並打印出符合大小標準的全部變位詞組。變位詞組是一堆單詞,全部單詞都包含徹底相同的字母,但順序不一樣,該程序在命令行上有兩個參數:(1)字典文件的名稱,(2)要打印出的變位詞組的最小尺寸,不打印包含少於指定最小值的單詞組的變位詞組。

找到變位詞組有一個標準技巧:對於字典中的每一個單詞,按字母順序排列單詞中的字母(即,將單詞的字母從新排序爲字母順序)並將條目放入多重映射,將字母順序排列的單詞映射到原始單詞。例如,單詞bad致使將abd條目映射爲bad以將其放入多重映射中,稍做思考就會發現,任何給定鍵映射到的全部單詞都構成一個變位詞組。迭代多重映射中的鍵,打印出符合大小約束的每一個變位詞組是一件簡單的事情。

如下程序是該技術的直接實現。

import java.util.*;
import java.io.*;

public class Anagrams {
    public static void main(String[] args) {
        int minGroupSize = Integer.parseInt(args[1]);

        // Read words from file and put into a simulated multimap
        Map<String, List<String>> m = new HashMap<String, List<String>>();

        try {
            Scanner s = new Scanner(new File(args[0]));
            while (s.hasNext()) {
                String word = s.next();
                String alpha = alphabetize(word);
                List<String> l = m.get(alpha);
                if (l == null)
                    m.put(alpha, l=new ArrayList<String>());
                l.add(word);
            }
        } catch (IOException e) {
            System.err.println(e);
            System.exit(1);
        }

        // Print all permutation groups above size threshold
        for (List<String> l : m.values())
            if (l.size() >= minGroupSize)
                System.out.println(l.size() + ": " + l);
    }

    private static String alphabetize(String s) {
        char[] a = s.toCharArray();
        Arrays.sort(a);
        return new String(a);
    }
}

在173,000字的字典文件上運行此程序,最小變位詞組大小爲8會產生如下輸出。

9: [estrin, inerts, insert, inters, niters, nitres, sinter,
     triens, trines]
8: [lapse, leaps, pales, peals, pleas, salep, sepal, spale]
8: [aspers, parses, passer, prases, repass, spares, sparse,
     spears]
10: [least, setal, slate, stale, steal, stela, taels, tales,
      teals, tesla]
8: [enters, nester, renest, rentes, resent, tenser, ternes,
     treens]
8: [arles, earls, lares, laser, lears, rales, reals, seral]
8: [earings, erasing, gainers, reagins, regains, reginas,
     searing, seringa]
8: [peris, piers, pries, prise, ripes, speir, spier, spire]
12: [apers, apres, asper, pares, parse, pears, prase, presa,
      rapes, reaps, spare, spear]
11: [alerts, alters, artels, estral, laster, ratels, salter,
      slater, staler, stelar, talers]
9: [capers, crapes, escarp, pacers, parsec, recaps, scrape,
     secpar, spacer]
9: [palest, palets, pastel, petals, plates, pleats, septal,
     staple, tepals]
9: [anestri, antsier, nastier, ratines, retains, retinas,
     retsina, stainer, stearin]
8: [ates, east, eats, etas, sate, seat, seta, teas]
8: [carets, cartes, caster, caters, crates, reacts, recast,
     traces]

許多這些詞彷佛有點虛僞,但這不是程序的錯;它們在字典文件中,這是使用的字典文件,它源自Public Domain ENABLE基準參考詞列表。


上一篇:Deque接口

下一篇:對象排序

相關文章
相關標籤/搜索