一、问题描述
实习时写过下面一段代码:
Collection allISynchros = synchroCache.values(); // synchroCache是HashMap
Collection npcISynchros = synchroNpcCache.values(); // synchroNpcCache是HashMap
allISynchros.addAll(npcISynchros);
运行时抛出java.lang.UnsupportedOperationException异常。
二、原因分析
Collection接口的实现与继承关系如下图:
AbstractCollection是Java集合框架中Collection接口的直接实现类,Collection的子接口的实现类大都继承AbstractCollection,比如AbstractList和AbstractSet。
AbstractCollection实现了几个方法,也定义了几个抽象方法留给子类实现,因此是一个抽象类。两个抽象方法:
public abstract Iterator iterator();
public abstract int size();
此外,注意add()和addAll()方法的实现:
/**
* {@inheritDoc}
*
* This implementation always throws an
* UnsupportedOperationException.
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
* @throws IllegalStateException {@inheritDoc}
*/
public boolean add(E e) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*
*
This implementation iterates over the specified collection, and adds
* each object returned by the iterator to this collection, in turn.
*
*
Note that this implementation will throw an
* UnsupportedOperationException unless add is
* overridden (assuming the specified collection is non-empty).
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
* @throws IllegalStateException {@inheritDoc}
*
* @see #add(Object)
*/
public boolean addAll(Collection extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
如果调用AbstractCollection的add()或addAll()方法,将抛出Unsupported OperationException异常。因此如果子类需要add的功能,需要重写add()方法。
文章开头的代码中synchroCache和synchroNpcCache均为HashMap,查看源代码,发现HashMap的values()方法实现如下:
/**
* Returns a {@link Collection} view of the values contained in this map.
* The collection is backed by the map, so changes to the map are
* reflected in the collection, and vice-versa. If the map is
* modified while an iteration over the collection is in progress
* (except through the iterator's own remove operation),
* the results of the iteration are undefined. The collection
* supports element removal, which removes the corresponding
* mapping from the map, via the Iterator.remove,
* Collection.remove, removeAll,
* retainAll and clear operations. It does not
* support the add or addAll operations.
*
* @return a view of the values contained in this map
*/
public Collection values() {
Collection vs = values;
if (vs == null) {
vs = new Values();
values = vs;
}
return vs;
}
final class Values extends AbstractCollection {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator iterator() { return new ValueIterator(); }
public final boolean contains(Object o) { return containsValue(o); }
public final Spliterator spliterator() {
return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
}
public final void forEach(Consumer super V> action) {
Node[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node e = tab[i]; e != null; e = e.next)
action.accept(e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
}
可以看出values()方法返回的vs属于Values内部类,该类继承自AbstractCollection,而且并未重写add()方法。因此如果对返回值调用addAll()或者add()方法,依然会抛出UnsupportedOperationException异常。因此今后在实际使用中要避免HashMap.values().add()&HashMap.values.addAll()。
三、解决方案
那么如果需要合并两个AbstractCollection该如何操作?
方案(1)将Collection转List,首先尝试强转的方法:
List allISynchros = (List)synchroCache.values();
强转报错java.lang.ClassCastException。因为Collection是List的父接口,而强制类型转换只能完成向上转型,即子类对象转为父类对象,反过来不行,因为子类有的方法父类可能没有。但将List向上转型为Collection是可行的。
后来看到ArrayList有个构造函数:
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public ArrayList(Collection extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
可以接受一个Collection的参数,返回一个List,实现了将Collection转换为List,满足要求:
List allISynchros = new ArrayList(synchroCache.values());
List npcISynchros = new ArrayList(synchroNpcCache.values());
之后再调用ArrayList的addAll()方法即可。ArrayList的add()方法是重写的,实现了add功能,不会抛异常:
allISynchros.addAll(npcISynchros);
方案(2)使用CollectionUtils.union(collection1,collection2)方法:
Collection allISynchros = synchroCache.values();
Collection npcISynchros = synchroNpcCache.values();
CollectionsUtils.union(allISynchros, npcISynchros);