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类媲美。

posted @ 2016-12-10 14:34  天涯海角路  阅读(502)  评论(0)    收藏  举报