【问题标题】:Functional/Immutable Data Structures for the JVM? [closed]JVM 的功能/不可变数据结构? [关闭]
【发布时间】:2010-10-01 14:02:01
【问题描述】:

有谁知道 Java/JVM 数据结构库提供熟悉的 Java 数据结构的功能(也称为不可变,或功能意义上的“持久”)等价物?

我所说的“功能性”是指对象本身是不可变的,而对这些对象的修改会返回与父对象共享相同内部结构的新对象(为了时间和空间上的效率;天真的实现可以只复制每次写的都是完整的)。

很像 Java 的并发库,这似乎不是我可以或应该自己实现的东西,所以如果有一个我可以在 JVM 中使用的函数式数据结构库会很好。

【问题讨论】:

    标签: java data-structures functional-programming jvm


    【解决方案1】:

    Clojure 的不可变和持久数据结构已被提取为 Java 库。您可以在http://github.com/krukow/clj-ds 找到它们。这些数据结构不依赖于 Clojure 运行时,因此可以在应用程序的类路径中没有 clojure.jar 的情况下使用。它们已经过泛型化,可以顺利处理 Java 代码。

    请注意,使用这些不可变数据结构在 Java 中可能不习惯。

    github页面没有jar可供下载。您必须检查源代码并自己构建 jar。

    【讨论】:

    • 这正是我想要的!
    • 好主意,只是代码已经 5 年没有维护了。当然,这个答案是 9 岁。
    【解决方案2】:

    试试Functional Java。它包含不可变的映射、集合、列表和树。然而,这个库不仅仅是不可变数据结构的集合!

    【讨论】:

      【解决方案3】:

      功能性和不可变性是大多数 Scala 集合库的核心属性。 Scala 编译到 JVM 并与 Java 很好地互操作。 Scala 语法也比 Clojure(Lisp 语法)之类的更接近 Java。

      这是 Scala 集合 API 的介绍页面。 http://www.scala-lang.org/docu/files/collections-api/collections.html

      【讨论】:

      • 我不会推荐 Scala。它是一种复杂的巴洛克语言,不兼容其自身的不同版本。
      【解决方案4】:

      尝试使用Guava,它有不可变的map、list、set。它还有一些实用程序来支持不可变集合,而不是修改底层对象,返回一个新对象。

      【讨论】:

      • Guava 非常棒,但它的不可变数据结构不是“功能性的”:变异操作抛出UnsupportedOperationException,而不是以某种方式返回更新的副本。后者将需要完全不同的 API。请注意,这不是对 Guava 的批评。开发人员只是将 API 与标准 Java 集合的兼容性视为比支持功能惯用语更重要。
      【解决方案5】:

      我可以理解为什么并发类很难编写:那里很容易出现难以发现的错误。

      在编写不可变的Collection 类时,Java 有一个避免此类错误的好方法:每种Collection 都有a method similar to java.util.Collections.unmodifiableSet(someSet),这将为您提供一个包装器,让您可以看到底层的Collection,但会阻止所有突变方法。不过,它只是一个包装器:如果您保留对它的引用,您仍然可以更改底层 Collection,所以不要这样做。此外,立即克隆并包装任何来自您控制之外的Collections,因为将它们交给您的程序员可能会在以后修改它们,从而改变您漂亮的不可变数据。


      如果你想创建一个库来为你处理所有这些迂腐的预防措施,这很耗时,但并不难。为了节省您的时间,我提供了一个最小优化的 FunctionalHashSet 示例,其中包含所有必要的突变预防。

      我通过查看Set 的API 列表创建了一个抽象超类(不要忘记toString)。对于非变异方法,我只是将它们传递给底层的Set。对于变异方法,我抛出 UnsupportedOperationException 并提供替代的函数式方法。

      这是那个抽象类,FunctionalSet

      import java.util.Collections;
      import java.util.Collection;
      import java.util.Set;
      import java.util.HashSet;
      import java.util.Iterator;
      
      public abstract class FunctionalSet<E> implements Set<E> {
        // final to prevent mutations through reassignment.
        protected final Set<E> set;
      
        // private to prevent any use of the default constructor.
        private   FunctionalSet()
          { this.set = null; }
        // unmodifiableSet to prevent mutations through Iterator and in subclasses.
        protected FunctionalSet(final Set<E> set)
          { this.set = Collections.unmodifiableSet(set); }
      
        public abstract FunctionalSet<E> clone();
        public abstract FunctionalSet<E> fAdd(final E element);
        public abstract FunctionalSet<E> fAddAll(final Collection<? extends E> elements);
        public abstract FunctionalSet<E> fRemove(final Object element);
        public abstract FunctionalSet<E> fRemoveAll(final Collection<?> elements);
        public abstract FunctionalSet<E> fRetainAll(final Collection<?> elements);
      
        protected abstract FunctionalSet<E> newFSet(final Set<E> newSet);
        protected abstract Set<E> newSet();
        protected abstract Set<E> cloneSet();
      
        protected final FunctionalSet<E> __fAdd(final E element) {
          if (set.contains(element)) return this;
          final Set<E> newSet = cloneSet();
          newSet.add(element);
          return newFSet(newSet);
        }
      
        protected final FunctionalSet<E> __fAddAll(final Collection<? extends E> elements) {
          if (set.containsAll(elements)) return this;
          final Set<E> newSet = cloneSet();
          newSet.addAll(elements);
          return newFSet(newSet);
        }
      
        protected final FunctionalSet<E> __fRemove(final Object element) {
          if (!set.contains(element)) return this;
          final Set<E> newSet = cloneSet();
          newSet.remove(element);
          return newFSet(newSet);
        }
      
        protected final Set<E> __fRemoveAll(final Collection<?> elements) {
          boolean hasNone = true;
          for (final Object element : elements) {
            if (set.contains(element)) {
              hasNone = false;
              break;
            }
          }
          if (hasNone) return this;
          final Set<E> newSet = cloneSet();
          newSet.removeAll(elements);
          return newFSet(newSet);
        }
      
        @SuppressWarnings("unchecked")
        protected final Set<E> __fRetainAll(final Collection<?> rawElements) {
          final Set elements = rawElements instanceof Set ? (Set) rawElements : new HashSet(rawElements);
          // If set is a subset of elements, we don't remove any of the elements.
          if (set.size() <= elements.size() && elements.containsAll(set)) return this;
          final Set<E> newSet = newSet();
          for (final E element : set) {
            if (elements.contains(element)) newSet.add(element);
          }
          return newFSet(newSet);
        }
      
        private final UnsupportedOperationException unsupported(final String call, final String goodCall) {
          return new UnsupportedOperationException(
            String.format(this.getClass().getName() + "s are immutable.  Use %s instead of %s.", goodCall, call)
          );
        }
      
        public final boolean add(final E element)
          { throw unsupported("add", "fAdd"); }
        public final boolean addAll(final Collection<? extends E> elements)
          { throw unsupported("addAll", "fAddAll"); }
        public final void clear()
          { throw unsupported("clear", "new " + this.getClass().getName() + "()"); }
        public final boolean remove(final Object element)
          { throw unsupported("remove", "fRemove"); }
        public final boolean removeAll(final Collection<?> elements)
          { throw unsupported("removeAll", "fRemoveAll"); }
        public final boolean retainAll(final Collection<?> elements)
          { throw unsupported("retainAll", "fRetainAll"); }
      
        public final boolean contains(final Object element)
          { return set.contains(element); }
        public final boolean containsAll(final Collection<?> elements)
          { return set.containsAll(elements); }
        public final boolean equals(final Object object)
          { return set.equals(object); }
        public final int hashCode()
          { return set.hashCode(); }
        public final boolean isEmpty()
          { return set.isEmpty(); }
        public final Iterator<E> iterator()
          { return set.iterator(); }
        public final int size()
          { return set.size(); }
        public final Object[] toArray()
          { return set.toArray(); }
        public final <E> E[] toArray(final E[] irrelevant)
          { return set.toArray(irrelevant); }
        public final String toString()
          { return set.toString(); }
      }
      

      在实施过程中,几乎没有什么可做的。我提供了一些构造函数和实用方法,并简单地使用所有变异方法的默认实现。

      这是一个实现,FunctionalHashSet。 :

      import java.util.Collection;
      import java.util.Set;
      import java.util.HashSet;
      
      public final class FunctionalHashSet<E> extends FunctionalSet<E> implements Cloneable {
        public static final FunctionalHashSet EMPTY = new FunctionalHashSet();
      
        public FunctionalHashSet()
          { super(new HashSet<E>()); }
        public FunctionalHashSet(final HashSet<E> set)
          { this(set, true); }
        @SuppressWarnings("unchecked")
        private FunctionalHashSet(final HashSet<E> set, final boolean clone)
          { super(clone ? (HashSet<E>) set.clone() : set); }
        public FunctionalHashSet(final Collection<E> elements)
          { this(new HashSet<E>(elements)); }
      
        protected FunctionalHashSet<E> newFSet(final Set<E> newSet)
          { return new FunctionalHashSet<E>((HashSet<E>) newSet, false); }
        protected HashSet<E> newSet()
          { return new HashSet<E>(); }
        @SuppressWarnings("unchecked")
        protected HashSet<E> cloneSet()
          { return new HashSet<E>(set); }
      
        public FunctionalHashSet<E> clone()
          { return this; }
        public FunctionalHashSet<E> fAdd(final E element)
          { return (FunctionalHashSet<E>) __fAdd(element); }
        public FunctionalHashSet<E> fAddAll(final Collection<? extends E> elements)
          { return (FunctionalHashSet<E>) __fAddAll(elements); }
        public FunctionalHashSet<E> fRemove(final Object element)
          { return (FunctionalHashSet<E>) __fRemove(element); }
        public FunctionalHashSet<E> fRemoveAll(final Collection<?> elements)
          { return (FunctionalHashSet<E>) __fRemoveAll(elements); }
        public FunctionalHashSet<E> fRetainAll(final Collection<?> elements)
          { return (FunctionalHashSet<E>) __fRetainAll(elements); }
      }
      

      一些注意事项:

      • 在每一种功能突变方法中,我都会检查是否真的会有任何变化。如果没有,我只返回完全相同的FunctionalSet
      • clone 中,我只返回完全相同的FunctionalSet
      • 运行setjava.util.Collections.unmodifiableSet 并声明它final 可以防止两种变异来源:用户不能通过Iterator 进行变异,并且实现者不能在其实现中意外变异。

      你可以接受这个并稍作修改以支持其他Collections。

      【讨论】:

      • 这个答案解决了不变性,但错过了持久数据结构的主要好处之一:最大化实例之间公共子结构的共享。
      【解决方案6】:

      Java 集合可能不像您希望的那样不可变,即使您应用 Collections.immutable()

      pure4j 提供 Clojure 持久性集合的修改版本(例如,包括泛型)以及对象的编译时不变性检查,为您提供一些不会更改集合的保证。

      Cornelius Mund 的项目https://github.com/cornim/ClojureCollections 也提供了 clojure 集合,但没有元素不变性保证,如果您需要的话。

      【讨论】:

        猜你喜欢
        • 2013-10-20
        • 1970-01-01
        • 2012-01-20
        • 1970-01-01
        • 2012-01-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多