前几天使用
HashSet 时遇到了一个
java.lang.UnsupportedOperationException 异常,在网上看了看前辈们的总结,再加上自己对 JDK 中相关 API 的学习,将出现该异常的原因基本弄清楚了。现在用一个简短的小实例演示这个异常的出现:
1 Map<String, String> m1 = new HashMap<String, String>();
2 Map<String, String> m2 = new HashMap<String, String>();
3
4 m1.put("foo", "foo");
5 m2.put("bar", "bar");
6
7 Set<String> s = m1.keySet();
8 s.addAll(m2.keySet());
9 //s.add("konglong");
10
11 /*Set<String> s = new HashSet<String>();
12 s.addAll(m1.keySet());
13 s.addAll(m2.keySet());*/
14
15 System.out.println(s);
注释掉第 8 行,释放第 9 行或者注释掉第 9 行,释放第 8 行,运行,就会发生 java.lang.UnsupportedOperationException 异常;但是如果注释掉第8、9行代码,释放第11、12、13行代码则不会出现该异常。原因主要在于引用 s 所指向的 Set 对象是 HashMap.keySet 方法的返回值,此方法在 JDK 文档中的描述为:
public Set<K> keySet()
- Returns a
Set
view of the keys contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation), the results of the iteration are #ff0000. The set supports element removal, which removes the corresponding mapping from the map, via the Iterator.remove, Set.remove, removeAll, retainAll, and clear operations. It does not support the add or addAll operations.
从上述描述中得知此方法返回的是当前 HashMap 对象中所包含的 key 的视图,这个视图是以什么格式保存的呢?它是以一个 HashSet 对象的格式(形式)保存的,也就是将一个一个的键值封装到一个 HashSet 对象中。但是并不意味着返回的这个 HashSet 对象就和原来的那个 HashMap 对象一刀两断,毫无干系了,它们仍然藕断丝连,相互影响着。这个 HashSet 对象是由原来那个 HashMap 对象在背后默默无闻地支持着。所以你对原来那个 HashMap 对象所做的修改会映射到这个 HashSet 对象,反之,对这个 HashSet 对象的修改亦会映射到原来那个 HashMap 对象。如果在对这个 set 对象进行迭代的同时修改了映射(但是通过迭代器自己的 remove 操作除外),则迭代结果是不确定的。该 set 支持元素的移除,通过 Iterator.remove、Set.remove、removeAll、retainAll 和 clear 操作可从该映射中移除相应的映射关系。但是它不支持 add 或 addAll 操作。
我们可以浅尝辄止,就此止步。但是很多时候我们稍稍坚持一下就可以得到更多的回报。它为什么不支持 add 或 addAll 操作呢?因为如果它支持这些操作,那么原来的 map 对象中必然多了几个 key,但是你不能只 put key 值进去,而没有对应的 value 值,这违背了映射本身的数据结构。
下一篇我将用实例演示一下通过 keySet() 方法返回的 Set 对象和它源自的 Map 对象之间的相互作用。