Функциональные/неизменяемые структуры данных для JVM?

Кто-нибудь знает библиотеку структуры данных Java/JVM, обеспечивающую функциональность (неизменяемый или постоянный в функциональном смысле эквивалент a.k.a.) эквивалентных знакомых структур данных Java?

Под "функциональным" я подразумеваю, что сами объекты являются неизменяемыми, а модификации этих объектов возвращают новые объекты, которые совместно используют те же внутренние элементы, что и родительский объект (для эффективности как времени, так и пространства; наивная реализация может просто скопировать все на каждой записи).

Как и библиотеки Java concurrency, это не похоже на то, что я могу или должен реализовать сам, поэтому было бы неплохо иметь библиотеку функциональной библиотеки данных, которую я могу использовать в JVM.

Ответы

Ответ 1

Clojure неизменяемые и постоянные структуры данных были извлечены как библиотека Java. Вы можете найти их в http://github.com/krukow/clj-ds. Эти структуры данных не зависят от среды выполнения Clojure и поэтому могут использоваться без clojure.jar в вашем пути к классу приложений. Они были созданы для бесперебойной работы с Java-кодом.

Пожалуйста, обратите внимание, что работа с этими неизменяемыми структурами данных не может быть идиоматичной в Java.

Страница github не содержит банку для загрузки. Вам нужно будет проверить источник и построить банку самостоятельно.

Ответ 2

Функциональные и неизменяемые являются основными свойствами большинства библиотек коллекции Scala. Scala компилируется в JVM и хорошо взаимодействует с Java. Синтаксис Scala также намного ближе к Java, чем что-то вроде синтаксиса Clojure (Lisp).

Здесь вводится страница к API-интерфейсу Scala. http://www.scala-lang.org/docu/files/collections-api/collections.html

Ответ 3

Попробуйте Функциональная Java. Он содержит неизменяемые карты, наборы, списки и деревья. Однако эта библиотека намного больше, чем просто набор неизменных структур данных!

Ответ 4

Попробуйте использовать Guava, он имеет неизменяемую карту, список, набор. Он также имеет некоторые утилиты для поддержки неизменяемой коллекции, которая вместо этого модифицирует базовый объект, возвращает новый объект.

Ответ 5

Я могу понять, почему классы concurrency трудно писать: очень легко получить там труднодоступные ошибки.

В Java есть хороший способ избежать таких ошибок при записи неизменяемых классов Collection: каждый вид Collection имеет метод, аналогичный java.util.Collections.unmodifiableSet(someSet), который предоставит вам оболочку, которая позволит вам увидеть базовый Collection, но блокирует все методы мутаций. Однако это только оболочка: вы все равно можете изменить базовый Collection, если вы держите ссылку на него лежащим, поэтому не делайте этого. Кроме того, немедленно клонируйте и оберните любой Collection, который поступает извне вашего контроля, так как программисты, которые их передали вам, могут впоследствии изменить их, изменив ваши хорошие неизменные данные.


Если вы хотите сделать библиотеку для обработки всех этих педантичных мер предосторожности для вас, это отнимает много времени, но не так сложно. Чтобы сэкономить ваше время, я включил пример минимально оптимизированного FunctionalHashSet со всей необходимой мутационной профилактикой.

Я сделал абстрактный суперкласс, спустив список API для Set (не забывая toString). Для методов nonmutating я просто передаю их в базовый 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
  • Запуск Set через java.util.Collections.unmodifiableSet и объявление его final предотвращает два источника мутации: пользователи не могут мутировать через Iterator, а разработчики не могут случайно мутировать в своих реализациях.

Вы можете принять это и немного изменить его для поддержки других Collection s.

Ответ 6

Коллекции Java, возможно, не так неизменны, как вы хотели бы, даже если вы применяете Collections.immutable()

pure4j предоставляет измененные версии постоянных коллекций Clojure (включая, например, generics), а также проверку целостности времени компиляции вашего объектов, давая вам некоторые гарантии того, что коллекции не могут измениться.

Проект Cornelius Mund, https://github.com/cornim/ClojureCollections также предоставляет коллекции Clojure без гарантий неизменяемости элементов, если это то, что вам нужно.