Функциональные / неизменяемые структуры данных для JVM? - PullRequest
15 голосов
/ 01 октября 2010

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

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

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

Ответы [ 6 ]

12 голосов
/ 01 октября 2010

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

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

6 голосов
/ 01 февраля 2011

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

6 голосов
/ 01 октября 2010

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

Вот начальная страница API коллекции Scala. http://www.scala -lang.org / доку / файлы / сборники-апи / collections.html

3 голосов
/ 02 октября 2010

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

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

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

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

3 голосов
/ 01 октября 2010

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

1 голос
/ 30 октября 2015

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

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

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

...