JDK中提供的不可变集合真的做到了不可变吗?

Posted c.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JDK中提供的不可变集合真的做到了不可变吗?相关的知识,希望对你有一定的参考价值。

JDK中提供的不可变集合真的做到了不可变吗?

为什么需要不可变集合

  1. 保证线程安全:在并发程序中,使用Immutable既保证线程安全性,也大大增强了并发 的效率(跟并发锁方式相比)。尤其当一个对象是值对象时,更应该考虑采用Immutable方式;
  2. 被不可信的类库使用时会很安全;
  3. 如果一个对象不需要支持修改操作(mutation),将会节省空间和时间的开销;经过分析,所有不可变的集合实现都比可变集合更加有效地利用内存;
  4. 可以当作一个常量来对待,并且这个对象在以后也不会被改变。
    将一个对象复制一份成immutable的,是一个防御性编程技术。

参考链接:https://www.jianshu.com/p/bf2623f18d6a

JDK中的不可变集合:Collections.unmodifiableXXX

在Java中,生成不可变集合主要是通过java.util包下的Collections工具类提供的方法。该类提供了很多生成不可变集合的方法,下面列举几个。

在这里插入图片描述

我们就来研究其中的一个方法unmodifiableCollection

    /**
     * Returns an <a href="Collection.html#unmodview">unmodifiable view</a> of the
     * specified collection. Query operations on the returned collection "read through"
     * to the specified collection, and attempts to modify the returned
     * collection, whether direct or via its iterator, result in an
     * {@code UnsupportedOperationException}.<p>
     *
     * The returned collection does <i>not</i> pass the hashCode and equals
     * operations through to the backing collection, but relies on
     * {@code Object}'s {@code equals} and {@code hashCode} methods.  This
     * is necessary to preserve the contracts of these operations in the case
     * that the backing collection is a set or a list.<p>
     *
     * The returned collection will be serializable if the specified collection
     * is serializable.
     *
     * @param  <T> the class of the objects in the collection
     * @param  c the collection for which an unmodifiable view is to be
     *         returned.
     * @return an unmodifiable view of the specified collection.
     */
    public static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c) {
        return new UnmodifiableCollection<>(c);
    }

通过方法签名可以看出,返回类型还是java.util包下常见的集合类型,但是他却是不可变的集合,它到底是如何做到的呢?

你要想知道什么是不可变集合,那我就带你研究。

我们一看方法中的内容就只是返回了一个UnmodifiableCollection对象,那我们就来研究一下这个UnmodifiableCollection类吧

UnmodifiableCollection类

  static class UnmodifiableCollection<E> implements Collection<E>, Serializable {
        private static final long serialVersionUID = 1820017752578914078L;

        final Collection<? extends E> c;

        UnmodifiableCollection(Collection<? extends E> c) {
            if (c==null)
                throw new NullPointerException();
            this.c = c;
        }

        public int size()                          {return c.size();}
        public boolean isEmpty()                   {return c.isEmpty();}
        public boolean contains(Object o)          {return c.contains(o);}
        public Object[] toArray()                  {return c.toArray();}
        public <T> T[] toArray(T[] a)              {return c.toArray(a);}
        public <T> T[] toArray(IntFunction<T[]> f) {return c.toArray(f);}
        public String toString()                   {return c.toString();}

        public Iterator<E> iterator() {
            return new Iterator<E>() {
                private final Iterator<? extends E> i = c.iterator();

                public boolean hasNext() {return i.hasNext();}
                public E next()          {return i.next();}
                public void remove() {
                    throw new UnsupportedOperationException();
                }
                @Override
                public void forEachRemaining(Consumer<? super E> action) {
                    // Use backing collection version
                    i.forEachRemaining(action);
                }
            };
        }

        public boolean add(E e) {
            throw new UnsupportedOperationException();
        }
        public boolean remove(Object o) {
            throw new UnsupportedOperationException();
        }

        public boolean containsAll(Collection<?> coll) {
            return c.containsAll(coll);
        }
        public boolean addAll(Collection<? extends E> coll) {
            throw new UnsupportedOperationException();
        }
        public boolean removeAll(Collection<?> coll) {
            throw new UnsupportedOperationException();
        }
        public boolean retainAll(Collection<?> coll) {
            throw new UnsupportedOperationException();
        }
        public void clear() {
            throw new UnsupportedOperationException();
        }

        // Override default methods in Collection
        @Override
        public void forEach(Consumer<? super E> action) {
            c.forEach(action);
        }
        @Override
        public boolean removeIf(Predicate<? super E> filter) {
            throw new UnsupportedOperationException();
        }
        @SuppressWarnings("unchecked")
        @Override
        public Spliterator<E> spliterator() {
            return (Spliterator<E>)c.spliterator();
        }
        @SuppressWarnings("unchecked")
        @Override
        public Stream<E> stream() {
            return (Stream<E>)c.stream();
        }
        @SuppressWarnings("unchecked")
        @Override
        public Stream<E> parallelStream() {
            return (Stream<E>)c.parallelStream();
        }
    }

一看这个类的内容,好家伙,居然用了装饰者模式,它本身实现了Collection接口,同时在内部持有一个Collection对象。而构造方法就是把传入进来的集合对象赋值给自己持有的Collection对象。接着就是装饰自己类的内部方法了,对于那些可以做修改操作的方法都进行了重新改造了,比如add,remove等方法,直接抛出UnsupportedOperationException异常。而对于不影响集合内容的方法,则直接委托给了持有的Collection对象。好家伙,又学到了一手装饰者模式了,JDK中还是用到蛮多设计模式的。

     @Test
    void test(){
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        Collection<String> unmodifiableCollection = Collections.unmodifiableCollection(list);
        unmodifiableCollection.add("c");
    }

运行的时候我们就可以发现会报java.lang.UnsupportedOperationException的异常。

但是我们自己研究就可以发现,这里说的不可变不是真正意义上的不可变,如果你只能拿到返回的UnmodifiableCollection对象,那确实是不可变的;但是如果你能拿到传进来的Collection对象,因为是引用,所以如果Collection对象变了,UnmodifiableCollection对象也会发生变化。我们来用代码演示一下吧。

     @Test
    void test(){
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        Collection<String> unmodifiableCollection = Collections.unmodifiableCollection(list);
        System.out.println(unmodifiableCollection);//[a, b]
        list.add("c");
        System.out.println(unmodifiableCollection);//[a, b, c]
    }

从上面的代码可以发现,如果传进来的list对象发生了变化,返回的不可变集合的内容也是会发生变化的。

可见,虽然无法从外部改变集合实际的值。但是当内部集合改变的时候,从外部再次获取集合,内容也变了。可见,不可变也只是相对的不可变。

参考链接

Java中的不变集合

以上是关于JDK中提供的不可变集合真的做到了不可变吗?的主要内容,如果未能解决你的问题,请参考以下文章

java代码之美---guava之Immutable(不可变)集合

在Java中String类为什么要设计成final?String真的不可变吗?其他基本类型的包装类也是不可变的吗?

ReadOnlyCollection vs Liskov - 如何正确建模可变集合的不可变表示

Doctrine 2 中的不可变集合?

2-JDK源码对你最有触动的是哪一段#集合

在 Scala 中添加不可变集合时,幕后发生了啥?