美文网首页程序员Java 杂谈
慎用Collection的add()接口

慎用Collection的add()接口

作者: loumoon | 来源:发表于2018-09-18 15:19 被阅读8次

    一、问题描述

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

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

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

    二、原因分析

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



      AbstractCollection是Java集合框架中Collection接口的直接实现类,Collection的子接口的实现类大都继承AbstractCollection,比如AbstractList和AbstractSet。
      AbstractCollection实现了几个方法,也定义了几个抽象方法留给子类实现,因此是一个抽象类。两个抽象方法:

        public abstract Iterator<E> iterator();
        public abstract int size();
    

      此外,注意add()和addAll()方法的实现:

    /**
         * {@inheritDoc}
         *
         * <p>This implementation always throws an
         * <tt>UnsupportedOperationException</tt>.
         *
         * @throws UnsupportedOperationException {@inheritDoc}
         * @throws ClassCastException            {@inheritDoc}
         * @throws NullPointerException          {@inheritDoc}
         * @throws IllegalArgumentException      {@inheritDoc}
         * @throws IllegalStateException         {@inheritDoc}
         */
        public boolean add(E e) {
            throw new UnsupportedOperationException();
        }
    
    /**
         * {@inheritDoc}
         *
         * <p>This implementation iterates over the specified collection, and adds
         * each object returned by the iterator to this collection, in turn.
         *
         * <p>Note that this implementation will throw an
         * <tt>UnsupportedOperationException</tt> unless <tt>add</tt> 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 <tt>remove</tt> operation),
         * the results of the iteration are undefined.  The collection
         * supports element removal, which removes the corresponding
         * mapping from the map, via the <tt>Iterator.remove</tt>,
         * <tt>Collection.remove</tt>, <tt>removeAll</tt>,
         * <tt>retainAll</tt> and <tt>clear</tt> operations.  It does not
         * support the <tt>add</tt> or <tt>addAll</tt> operations.
         *
         * @return a view of the values contained in this map
         */
        public Collection<V> values() {
            Collection<V> vs = values;
            if (vs == null) {
                vs = new Values();
                values = vs;
            }
            return vs;
        }
    
        final class Values extends AbstractCollection<V> {
            public final int size()                 { return size; }
            public final void clear()               { HashMap.this.clear(); }
            public final Iterator<V> iterator()     { return new ValueIterator(); }
            public final boolean contains(Object o) { return containsValue(o); }
            public final Spliterator<V> spliterator() {
                return new ValueSpliterator<>(HashMap.this, 0, -1, 0, 0);
            }
            public final void forEach(Consumer<? super V> action) {
                Node<K,V>[] 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<K,V> 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<ISynchro> allISynchros = (List<ISynchro>)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<ISynchro> allISynchros = new ArrayList<ISynchro>(synchroCache.values());
    List<ISynchro> npcISynchros = new ArrayList<ISynchro>(synchroNpcCache.values());
    

    之后再调用ArrayList的addAll()方法即可。ArrayList的add()方法是重写的,实现了add功能,不会抛异常:

    allISynchros.addAll(npcISynchros);
    

      方案(2)使用CollectionUtils.union(collection1,collection2)方法:

    Collection<ISynchro> allISynchros = synchroCache.values();
    Collection<ISynchro> npcISynchros = synchroNpcCache.values();
    CollectionsUtils.union(allISynchros, npcISynchros);
    

    相关文章

      网友评论

        本文标题:慎用Collection的add()接口

        本文链接:https://www.haomeiwen.com/subject/gjzunftx.html