javaapi接口篇(二)上?bMap接口Map是一个将键映射为值的对象。一个映射不能包含重复键:每个键最多能映射一个值。Map接口如下所示:publicinterfaceMap{//BasicOperationsObjectput(Objectkey,Objectvalue);Objectget(Objectkey);Objectremove(Objectkey);booleancontainsKey(Objectkey);booleancontainsValue(Objectvalue);intsize();booleanisEmpty();//BulkOperationsvoidputAll(Mapt);voidclear();//CollectionViewspublicSetkeySet();publicCollectionvalues();publicSetentrySet();//InterfaceforentrySetelementpublicinterfaceEntry{ObjectgetKey();ObjectgetValue();ObjectsetValue(Objectvalue);}}JDK包含两个新的通用Map实现,一个是HashMap,它将它的项存储在一个哈希表中,是一种最好的实现;另一个是TreeMap,它将它的项存储在一个红-黑树上,它可保证迭代的顺序。另外,Hashtable已被改进以实现Map。与哈希表的比较如果你使用过Hashtable,你应该已经熟悉了Map的一般风格(当然Map是一个接口,而Hashtable是一个具体的实现)。以下是它们的主要区别:Map提供Collection视图,作为Enumeration对象的替代直接支持迭代过程。Collection视图极大地提高了接口的可表达性,正如后续课程将讲到的。Map允许你在键、值或键-值对上进行迭代;Hashtable则不提供第三个选项。Map提供了在迭代过程中删除项的安全途径;Hashtable则不能。进一步讲,Map修补了Hashtable接口上的某些小缺陷。Hashtable具有一个称作contains的方法,如果Hashtable包含一个给定值,它将返回true。从它的名字上理解,你可能期望如果Hashtable包含一个给定的key,这个方法也会返回一个true,因为键是一个Hashtable的主要存取机制。Map接口通过将这个方法重新命名为containsValue,从而消除了引起混乱的来源;同时也改善了接口的一致性:containsValue与containsKey可很好地对应并行。基本操作基本操作(put,get,remove,containsKey,containsValue,s,a和isEmpty)的功能与它们在Hashtable中的对等物非常相似。下面的简单程序针对参数列表中的词汇生成一个频率表。频率表将每个词和它在参数列表中所出现的次数相映射。importjava.util.*;publicclassFreq{privatestaticfinalIntegerONE=newInteger(1);publicstaticvoidmain(Stringargs[]){Mapm=newHashMap();//Initializefrequencytablefromcommandlinefor(inti=0;i$#@60;args.length;i++){Integerfreq=(Integer)m.get(args[i]);m.put(args[i],(freq==null?ONE:newInteger(freq.intValue()+1)));}System.out.println(m.size()+distinctwordsdetected:);System.out.println(m);}}有关这个程序的一个小技巧是put语句的第二个参数。这是一个条件表达式,它的效果是如果这个词汇以前从未被看到过,则将频率设置为one,如果这个词汇已经被看到,则设置为当前值加1。让我们来运行这个程序:%javaFreqifitistobeitisuptometodelegate8distinctwordsdetected:{to=3,me=1,delegate=1,it=2,is=2,if=1,be=1,up=1}假设你更喜欢以字母顺序排列的频率表,那么所有你要做的工作就是将Map的实现类型从HashMap改变为TreeMap.这四个字母的改变,使该程序从相同的命令行生成了如下输出:8distinctwordsdetected:{be=1,delegate=1,if=1,is=2,it=2,me=1,to=3,up=1}这个接口酷吗?怎么样?正如Set和List接口一样,Map加强了对equals和hashCode方法的要求,于是,两个Map对象可做逻辑等同性比较而不必考虑它们的实现类型。如果它们显示了相等的键-值映射,则两个Map对象是相等的。按惯例,所有的Map实现可提供构造函数;该构造函数提取一个Map对象并将这个新的Map初始化,使之包含特定Map中的所有键-值映射。这个标准Map构造函数是为Collection实现而设计的标准对象集构造函数的完全对等物。它允许调用者创建一个期望的实现类型的Map;该实现类型初始包含另一个Map的所有映射。而不考虑其它Map的实现类型。例如,假设你有一个命名为m的Map,则下列一行代码创建了一个新的HashMap,它初始包含所有与m相同的键-值映射:Mapcopy=newHashMap(m);批量操作(BulkOperations)clear操作所完成的工作正象其词义上所表达的那样:它从Map中删除所有映射。putAll操作是Collection接口中的addAll操作的Map对等物;它可将一个Map转储至另一个,除此之外,它还有一个更微妙的用处。假设一个Map被用来表示属性-值对(attribute-valuepairs);putAll操作将与标准Map构造函数一起提供一种用默认值创建属性表的简捷方法。以下是一个可演示此种技术的静态方法:staticMapnewAttributeMap(Mapdefaults,Mapoverrides){Mapresult=newHashMap(defaults);result.putAll(overrides);returnresult;}Collection视图Collection视图方法允许以三种方式将一个Map作为一个Collection来视图:keySet:包含在Map中的键的Set。values:包含在Map中的值的Collection。该Collection不是一个Set,因为多个键可映射相同的值。entrySet:包含在Map中的键-值对的Set。Map接口提供了一个小的被称作Map.Entry的嵌套接口,它是在这个Set中的元素的类型。Collection视图提供了在Map上进行迭代的唯一方法。下面的例子给出了在一个Map上迭代键的标准惯用程序:for(Iteratori=m.keySet().iterator();i.hasNext();)System.out.println(i.next());对值进行迭代的惯用程序是类似的。这是迭代键-值对的惯用程序:for(Iteratori=m.entrySet().iterator();i.hasNext();){Map.Entrye=(Map.Entry)i.next();System.out.println(e.getKey()+:+e.getValue());}第一次提交这些惯用程序时,许多人考虑到每次调用一个Collection视图时,Map都必须创建一个新的Collection对象,因而担心其速度慢。请放心:这是不可能的。如果每次一个Map被要求给出一个特定的Collection视图时,没有道理Map不能总是返回相同的对象。这恰恰是所有JDK的Map实现所要作的事。用所有三个Collection视图,调用一个Iterator的remove的操作可从后备Map中删除相关项(假设该Map支持删除)。用entrySet视图,通过在迭代过程中调用一个Map.Entry的setValue方法(再一次假设该Map支持值的更改),也可能改变与一个键相关的值。请注意这是在迭代过程中更改一个Map的唯一安全途径。Collection视图支持它的所有形式的元素删除:remove,removeAll,retainAll,和clear操作,以及Iterator.remove操作(然而,这是建立在假设后备Map支持元素删除的基础之上)。Collection视图在任何情况下都不支持元素增加。对keySet和values视图这是无意义的,而对entrySet视Collection视图的奇特用法:Map代数在应用Collection视图时,批量操作(containsAll,removeAll和retainAll)是一个惊人的有力工具。假设你要了解一个Map是否是另一个的子映射(submap),也就是说,第一个Map是否包含第二个的全部键-值映射,请看下面惯用程序的小技巧:if(m1.entrySet().containsAll(m2.entrySet())){..}对照类似行,假设你要了解两个Map对象是否包含所有所有相同键的映射:if(m1.keySet().equals(m2.keySet())){...}图来说,这是没必要的,因为后备Map的put和putAll提供了相同的功能。假设你具有一个映射,代表一个属性-值对集合;以及两个sets,表示要求的属性和允许的属性(允许的属性包括要求的属性)。下列代码可判定该属性映射是否符合那些限定条件,如果不符合,则打印详细的出错消息:booleanvalid=true;Setattributes=attributeMap.keySet();if(!attributes.containsAll(requiredAttributes)){Setmissing=newHashSet(requiredAttributes);missing.removeAll(attributes);System.out.println(Missingrequiredattributes:+missing);valid=false;}if(!permissibleAttributes.containsAll(attributes)){Setillegal=newHashSet(attributes);illegal.removeAll(permissibleAttributes);System.out.println(Containsillegalattributes:+illegal);valid=false;}if(valid)System.out.println(OK);假设你想了解由两个Map对象公用的所有键:SetcommonKeys=newHashSet(a.keySet());commonKeys.retainAll(b.keySet());类似的惯用程序使你可以获得公共值以及公共键-值对。要获得公共键-值对,则需格外小心;因为结果Set的元素(即Map.Entry对象)在Map被更改后,可能是无效的。到目前为止,所有惯用程序都是非破坏性的:它们不更改后备Map。下面是一些更改后备Map的例子。假设你要删除一个Map与另一个Map所共有的所有键-值对:m1.entrySet().removeAll(m2.entrySet());假设你要从一个Map中删除所有在另一个Map中具有映射的键:m1.keySet().removeAll(m2.keySet());当你在同样的批量操作中开始混合键和值时,发生了什么事情呢?假设你有一个称作managers