Functional/Immutable Data Structures for the JVM? [closed]

痞子三分冷 提交于 2019-12-20 11:54:07

问题


Does anyone know of a Java/JVM data structure library providing functional (a.k.a. immutable, or "persistent" in the functional sense) equivalents of the familiar Java data structures?

By "functional" I mean that the objects themselves are immutable, while modifications to those objects return new objects sharing the same internals as the parent object where appropriate (for efficiency in both time and space; a naïve implementation could just copy the whole thing on every write).

Much like Java's concurrency libraries, this doesn't seem like something I can or should implement myself, so it would be nice to have a functional data structure library I can use in the JVM.


回答1:


Clojure's immutable and persistent data structures have been extracted as a Java library. You can find them at http://github.com/krukow/clj-ds. These data structures are not dependent on the Clojure runtime and hence can be used without the clojure.jar in your application's classpath. They have been generified to work smoothly with Java code.

Please make a note that working with these immutable data structures may not be idiomatic in Java.

The github page does not have a jar for download. You will have to checkout the source and build the jar yourself.




回答2:


Functional and immutable are core properties of most of the Scala collection libraries. Scala compiles to the JVM and interoperates well with Java. The Scala syntax is also much closer to Java than something like Clojure (Lisp syntax).

Here's the intro page to the Scala collection API. http://www.scala-lang.org/docu/files/collections-api/collections.html




回答3:


Try Functional Java. It contains immutable maps, sets, lists, and trees. However, this library is much more than just a collection of immutable data structures!




回答4:


Try using Guava, it has immutable map, list, set. It also has some utilities to support immutable collection that instead modifying the underlying object, returns a new object.




回答5:


I can understand why the concurrency classes are difficult to write: it's very easy to get difficult-to-see bugs there.

Java has a nice way to avoid such bugs when writing immutable Collection classes: each kind of Collection has a method similar to java.util.Collections.unmodifiableSet(someSet), which will give you a wrapper that lets you see the underlying Collection but blocks all mutation methods. It's only a wrapper, though: you can still change the underlying Collection if you keep a reference to it lying around, so don't do that. Also, immediately clone and wrap any Collections that come from outside your control, since the programmers who handed them to you might modify them later, mutating your nice immutable data.


If you want to make a library to handle all these pedantic precautions for you, it's time-consuming but not that hard. To save you time, I've included an example of a minimally-optimized FunctionalHashSet with all necessary mutation prevention.

I made an abstract superclass by going down the API listing for Set (not forgetting toString). For nonmutating methods, I simply pass them on to the underlying Set. For mutating methods, I throw UnsupportedOperationException and provide alternative functional-style methods.

Here's that abstract class, 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(); }
}

In the implementation, there's very little left to do. I supply a few constructors and utility methods and simply use the default implementations of all the mutating methods.

Here's an implementation, 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); }
}

A few notes :

  • In every single functional mutation method, I check whether there will actually be any changes. If not, I just return the exact same FunctionalSet.
  • In clone, I just return the exact same FunctionalSet
  • Running set through java.util.Collections.unmodifiableSet and declaring it final prevents two sources of mutation: users can't mutate through the Iterator and implementors can't accidentally mutate in their implementations.

You can take this and modify it a bit to support other Collections.




回答6:


Java collections are perhaps not as immutable as you would like, even when you apply Collections.immutable()

pure4j provides modified versions of Clojure persistent collections (including generics, for example) and also compile-time immutability checking of your objects, giving you some guarantees that collections cannot change.

Cornelius Mund's project, https://github.com/cornim/ClojureCollections also provides the clojure collections, without the element immutability guarantees, if that is what you need.



来源:https://stackoverflow.com/questions/3839870/functional-immutable-data-structures-for-the-jvm

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!