Set和Map的关系
3.1 Set和Map
Set代表一种集合元素无序、不可重复的集合,Map则代表一种由多个key-value对组成的集合,Map集合类似于传统的关联数组。从表面上看,它们之间的相似性很少,但实际上Map和Set之间有莫大的关联,可以说,Map集合是Set集合的扩展。
3.1.1 Set和Map的关系
在看Set和Map之间的关系之前,先来看看Set集合的继承体系,如图3.1所示。

再来看看Map集合的继承体系,如图3.2所示。

仔细观察图3.2中Map集合的继承体系里被灰色覆盖的区域,可以发现,这些Map集合的接口、实现类的类名和Set集合的接口、实现类的类名完全相似,把Map后缀改为Set后缀即可。Set集合和Map集合的对应关系如下:
Set←→Map
EnumSet←→EnumMap
SortedSet←→SortedMap
TreeSet←→TreeMap
NavigableSet←→NavigableMap
HashSet←→HashMap
LinkedHashSet←→LinkeHashMap
这些接口和类名如此相似绝不是偶然的现象,肯定有其必然的原因。
从表面上看,这两种集合并没有太多的相似之处,但如果只考察Map集合的key,不难发现,这些Map集合的key具有一个特征:所有的key不能重复,key之间没有顺序。也就是说,如果将Map集合的所有key集中起来,那么这些key就组成了一个Set集合。实际上,Map集合提供了如下方法来返回所有key组成的Set集合。
Set<K> keySet()
由此可见,Map集合的所有key将具有Set集合的特征,只要把Map的所有key集中起来看,那它就是一个Set,这实现了从Map到Set的转换。其实,还可以实现从Set到Map的扩展——对于Map而言,相当于每个元素都是key-value对的Set集合。
换一种思维来理解Map集合,如果把Map集合中的value当成key的“附属物”(实际上也是,对于一个Map集合而言,只要给出指定的key,Map总是可以根据该key快速查询到对应的value),那么Map集合在保存key-value对时只要考虑key即可。
对于一个Map集合而言,它本质上是一个关联数组,如图3.3所示。
对于图3.3所示的关联数组,其实可以改为使用一个Set集合来保存它们,反正上面关联数组中的key-value对之间有严格的对应关系,那么干脆将key-value对捆绑在一起对待,如图3.4所示。

为了把Set扩展成Map,可以考虑新定义一个SimpleEntry类,该类代表一个key-value对;当Set集合的集合元素都是SimpleEntry对象时,该Set集合就能被当成Map使用。下面程序示范了如何将一个Set集合扩展成Map集合。
程序清单:codes\03\3.1\Set2Map.java
class SimpleEntry<K , V>
implements Map.Entry<K , V>, java.io.Serializable
{
private final K key;
private V value;
// 定义如下两个构造器
public SimpleEntry(K key, V value)
{
this.key = key;
this.value = value;
}
public SimpleEntry(Map.Entry<? extends K
, ? extends V> entry)
{
this.key = entry.getKey();
this.value = entry.getValue();
}
// 获取key
public K getKey()
{
return key;
}
// 获取value
public V getValue()
{
return value;
}
// 改变该key-value对的value值
public V setValue(V value)
{
V oldValue = this.value;
this.value = value;
return oldValue;
}
// 根据key比较两个SimpleEntry是否相等
public boolean equals(Object o)
{
if (o == this)
{
return true;
}
if (o.getClass() == SimpleEntry.class)
{
SimpleEntry se = (SimpleEntry)o;
return se.getKey().equals(getKey());
}
return false;
}
// 根据key计算hashCode
public int hashCode()
{
return key == null ? 0 : key.hashCode();
}
public String toString()
{
return key + "=" + value;
}
}
// 继承HashSet实现一个Map
public class Set2Map<K , V>
extends HashSet<SimpleEntry<K , V>>
{
// 实现清空所有key-value对的方法
public void clear()
{
super.clear();
}
// 判断是否包含某个key
public boolean containsKey(K key)
{
return super.contains(
new SimpleEntry<K , V>(key ,null));
}
// 判断是否包含某个value
boolean containsValue(Object value)
{
for (SimpleEntry<K , V> se : this)
{
if (se.getValue().equals(value))
{
return true;
}
}
return false;
}
// 根据指定的key取出对应的value
public V get(Object key)
{
for (SimpleEntry<K , V> se : this)
{
if (se.getKey().equals(key))
{
return se.getValue();
}
}
return null;
}
// 将指定的key-value对放入集合中
public V put(K key, V value)
{
add(new SimpleEntry<K , V>(key ,value));
return value;
}
// 将另一个Map的key-value对放入该Map中
public void putAll(Map<? extends K,? extends V> m)
{
for (K key : m.keySet())
{
add(new SimpleEntry<K , V>(key , m.get(key)));
}
}
// 根据指定的key删除指定的key-value对
public V removeEntry(Object key)
{
for (Iterator<SimpleEntry<K , V>> it = this.iterator()
; it.hasNext() ; )
{
SimpleEntry<K , V> en = (SimpleEntry<K , V>)it.next();
if (en.getKey().equals(key))
{
V v = en.getValue();
it.remove();
return v;
}
}
return null;
}
// 获取该Map中包含多少个key-value对
public int size()
{
return super.size();
}
}
上面程序中的粗体字代码定义了一个SimpleEntry<K,V>类。当一个Set的所有集合元素都是SimpleEntry<K,V>对象时,该Set就变成了一个Map<K,V>集合。
接下来,程序以HashSet<SimpleEntry<K,V>>为父类派生了一个子类Set2Map<K,V>,这个Set2Map<K,V>扩展类完全可以被当成Map使用,因此上面程序中的Set2Map<K,V>类中也提供了Map集合应该提供的绝大部分方法。
下面程序简单测试了扩展出来的“Map”集合。
程序清单:codes\03\3.1\Set2MapTest.java
public class Set2MapTest
{
public static void main(String[] args)
{
Set2Map<String , Integer> scores = new Set2Map<>();
// 将key-value对放入集合中
scores.put("语文" , 89);
scores.put("数学" , 83);
scores.put("英文" , 80);
System.out.println(scores);
// 访问Map集合里包含的key-value对
System.out.println(scores.size());
scores.removeEntry("数学");
System.out.println("删除key为\"数学\"的Entry之后:" + scores);
// 根据key取出value
System.out.println("语文成绩:" + scores.get("语文"));
// 判断是否包含指定的key
System.out.println("是否包含\"英文\"key :"
+ scores.containsKey("英文"));
// 判断是否包含指定的value
System.out.println("是否包含 82 value :"
+ scores.containsValue(82));
// 清空集合
scores.clear();
System.out.println("执行clear()方法之后的集合:" + scores);
}
}
上面程序完全将Set2Map当成一个Map集合使用,包括把key-value对放入该集合中,根据key取得value,等等。运行上面程序,结果如图3.5所示。

根据此处介绍的这个程序不难看出,只要对传统的Set稍做改造,就可以将Set改造成Map集合,而且,这个Map集合在功能上几乎可以与系统提供的Map类媲美。

浙公网安备 33010602011771号