Java中的不可变集合

时间:2013-11-14 15:28:02

标签: java list collections immutability

我最近开始查看GUAVA的集合,即ImmutableList,这看起来相当麻烦(使用构建器实例等)。是否有一个库可以模仿集合应该如何表现的更“自然”的方式(scala的http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.List就是一个例子)。我想要一些允许添加/删除等但保留不变性的东西,并且可能出于性能目的,重用旧列表的部分内容。

5 个答案:

答案 0 :(得分:3)

Goldman Sachs Collection library

中有一个ImmutableArrayList(和其他不可变集合)

参见ImmutableArrayList.java,newWith(T t)和newWithout(T t)方法

答案 1 :(得分:1)

如果我理解正确,您需要一个具有方便的添加/删除方法的不可变列表,这些方法返回新的列表实例,这些实例尽可能多地重用原始列表结构。你可以这样做:

public abstract class ImmutableList<T> implements Iterable<T> {
    /**
     * Adds an element to the head of the list, returning the new list.
     *
     * @param o The element to be added to the list.
     * @return The list consisting of the element <var>o</var> followed by
     *         this list.
     */
    public final ImmutableList<T> add(final T o) {
        return new Node<>(o, this);
    }

    /**
     * Removes the element <var>o</var> resulting in a new list which
     * is returned to the caller.
     *
     * @param o The object to be removed from the list.
     * @return A list consisting of this list with object <var>o</var> removed.
     */
    public abstract ImmutableList<T> remove(final T o);

    public abstract boolean isEmpty();
    public abstract int size();

    public abstract boolean contains(final T o);

    private ImmutableList() {}

    /**
     * Returns a "standard" enumeration over the elements of the list.
     */
    public Iterator<T> iterator() {
        return new NodeIterator<>(this);
    }

    /**
     * The empty list.  Variables of type ImmutableList should be
     * initialised to this value to create new empty lists.
     */
    private static final ImmutableList<?> EMPTY = new ImmutableList<Object>() {
        @Override
        public ImmutableList<Object> remove(final Object o) {
            return this;
        }

        @Override
        public boolean isEmpty() {
            return true;
        }

        @Override
        public int size() {
            return 0;
        }

        @Override
        public boolean contains(final Object o) {
            return false;
        }
    };

    @SuppressWarnings("unchecked")
    public static <T> ImmutableList<T> empty() {
        return (ImmutableList<T>)EMPTY;
    }

    public static <T> ImmutableList<T> create(final T head) {
        return new Node<>(head, ImmutableList.<T>empty());
    }

    static class Node<T> extends ImmutableList<T> {
        private final int _size;

        private Node(final T element, final ImmutableList<T> next) {
            _element = element;
            _next = ArgumentHelper.verifyNotNull(next, "next");
            _size = next.size() + 1;
        }

        public ImmutableList<T> remove(final T old) {
            if (_element == old) {
                return _next;
            }
            else {
                final ImmutableList<T> n = _next.remove(old);
                if (n == _next) {
                    return this;
                }
                else {
                    return new Node<>(_element, n);
                }
            }
        }

        @Override
        public boolean isEmpty() {
            return false;
        }

        @Override
        public int size() {
            return _size;
        }

        @Override
        public boolean contains(final T o) {
            return Objects.equals(_element, o) || _next.contains(o);
        }

        private final T _element;
        private final ImmutableList<T> _next;
    }

    private class NodeIterator<T> implements Iterator<T> {
        private ImmutableList<T> _current;

        private NodeIterator(final ImmutableList<T> head) {
            _current = ArgumentHelper.verifyNotNull(head, "head");
        }

        public boolean hasNext() {
            return !_current.isEmpty();
        }

        public T next() {
            final T result = ((Node<T>)_current)._element;
            _current = ((Node<T>)_current)._next;
            return result;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
}

对于此实现,通过将项添加到ImmutableList.empty()来构建新列表。

请注意,这不是一个特别好的实现;新元素将附加到列表的开头,而不是结尾。但也许这会让你知道从哪里开始。

答案 2 :(得分:0)

您的要求表明您误解了这些工具的用途。不可变列表总是需要构建器,因为在调用构造函数后它不能更改。这就是不可改变的全部目的。他们永远不会改变。没有“设置阶段。”

如果您不需要,只需将现有列表包装在Collections.unmodifiableList()

当然,缺点是某人可能会保留对原始列表的引用并对其进行修改。或者黑客可能会查看包装器,获取基础列表并开始搞乱它。

答案 3 :(得分:0)

Clojure包含这样的不可变(或持久)collections

简单地说,添加或删除新元素会返回一个新集合,通常 通过巧妙使用Trie类型数据结构来重用旧集合的大部分内容。

就他们自己而言,这些不适合直接使用Java。

Pure4j试图将这些(以及Clojure提倡的基于不可变/值的样式)移植到Java语言中。这可能是你追求的目标。

免责声明:我是Pure4J的开发者

答案 4 :(得分:-1)

不可变意味着不改变的,所以答案是否定的。你不能改变一个不可变的列表(哇!)。只要您想要使用某些内容初始化列表并将其保留在列表实用程序中,那么随列表实用程序一起提供的构建器很好,因此我建议您查看应用程序的体系结构。

关于旧列表的重用部分 - 如果列表是可变的,那么通过对其进行变异,您将冒着导出当前列表的状态的不变性的风险。但是,如果实例都是不可变的,那么实际上引用前面的列表,因为只要它没有改变就可以重用它的部分,并且不可变的项不会改变。