慎用Collection的add()接口

一、问题描述

  实习时写过下面一段代码:

Collection allISynchros = synchroCache.values(); // synchroCache是HashMap
Collection npcISynchros = synchroNpcCache.values(); // synchroNpcCache是HashMap
allISynchros.addAll(npcISynchros);

  运行时抛出java.lang.UnsupportedOperationException异常。

二、原因分析

  Collection接口的实现与继承关系如下图:


慎用Collection的add()接口_第1张图片

  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 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 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 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);

你可能感兴趣的:(慎用Collection的add()接口)