Стойкие структуры данных в Java

Кто-нибудь знает библиотеку или некоторые, по крайней мере, некоторые исследования по созданию и использованию постоянных структур данных в Java? Я не отношусь к упорству как к долговременному хранению, а к сохранности с точки зрения неизменности (см. запись в Википедии).

В настоящее время я изучаю различные способы моделирования api для постоянных структур. Использование строителей кажется интересным решением:

// create persistent instance
Person p = Builder.create(Person.class)
             .withName("Joe")
             .withAddress(Builder.create(Address.class)
                 .withCity("paris")
                 .build())
              .build();

// change persistent instance, i.e. create a new one 
Person p2 = Builder.update(p).withName("Jack");

Person p3 = Builder.update(p)
              .withAddress(Builder.update(p.address())
                .withCity("Berlin")
                .build)
            .build();

Но это все еще кажется несколько запутанным. Любые идеи?

Ответы

Ответ 1

Я предполагаю, что очевидный выбор:

o Переключитесь на временную структуру данных (строитель) для обновления. Это вполне нормально. StringBuilder для String манипуляции, например. В качестве примера.

Person p3 =
    Builder.update(p)
    .withAddress(
        Builder.update(p.address())
       .withCity("Berlin")
       .build()
    )
    .build();

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

final Person p3 = p
    .withAddress(
        p.address().withCity("Berlin")
    );

o Разверните структуру данных во множество переменных и перекомбинируйте с помощью одного огромного и запутанного конструктора.

final Person p3 = Person.of(
    p.name(),
    Address.of(
       p.house(), p.street(), "Berlin", p.country()
    ),
    p.x(),
    p.y(),
    p.z()
 );

o Используйте интерфейсы обратного вызова для предоставления новых данных. Еще более шаблонный.

final Person p3 = Person.of(new PersonInfo(
    public String  name   () { return p.name(); )
    public Address address() { return Address.of(new AddressInfo() {
       private final Address a = p.address();
       public String house  () { return a.house()  ; }
       public String street () { return a.street() ; }
       public String city   () { return "Berlin"   ; }
       public String country() { return a.country(); }
    })),
    public Xxx     x() { return p.x(); }
    public Yyy     y() { return p.y(); }
    public Zzz     z() { return p.z(); }
 });

o Используйте неприятные хаки, чтобы сделать поля временно доступными для кода.

final Person p3 = new PersonExploder(p) {{
    a = new AddressExploder(a) {{
        city = "Berlin";
    }}.get();
}}.get();

(Как ни странно, я просто поставил копию чистых функциональных структур данных Криса Окасаки.)

Ответ 2

Строители сделают ваш код слишком многословным для использования. На практике почти все неизменные структуры данных, которые я видел, проходят через конструктор. Для чего стоит, вот хорошая серия сообщений, описывающих неизменяемые структуры данных в С# (которые должны легко конвертироваться в Java):

С# и Java являются чрезвычайно подробными, поэтому код в этих статьях довольно пугающий. Я рекомендую изучить OCaml, F # или Scala и ознакомиться с неизменностью этих языков. После того как вы освоите технику, вы сможете более точно применить тот же стиль кодирования к Java.

Ответ 3

Посмотрите Функциональная Java. В настоящее время при наличии постоянных структур данных:

  • Одиночный список (fj.data.List)
  • Ленивый односвязный список (fj.data.Stream)
  • Непустой список (fj.data.NonEmptyList)
  • Дополнительное значение (контейнер длиной 0 или 1) (fj.data.Option)
  • Установить (fj.data.Set)
  • Многодорожее дерево (дерево a.k.a. rose) (fj.data.Tree)
  • Неизменяемая карта (fj.data.TreeMap)
  • Продукты (кортежи) arity 1-8 (fj.P1..P8)
  • Векторы arity 2-8 (fj.data.vector.V2..V8)
  • Указанный список (fj.data.Zipper)
  • Указанное дерево (fj.data.TreeZipper)
  • Типовой, общий гетерогенный список (fj.data.hlist.HList)
  • Неизменяемые массивы (fj.data.Array)
  • Двойной тип объединения (fj.data.Either)

Ряд примеров использования предоставляется с бинарным дистрибутивом. Источник доступен под лицензией BSD от Код Google.

Ответ 4

Я реализовал несколько постоянных структур данных в Java. Все открытые исходники (GPL) в коде Google для всех, кто заинтересован:

http://code.google.com/p/mikeralib/source/browse/#svn/trunk/Mikera/src/mikera/persistent

К основным из них относятся:

  • Постоянный изменяемый тестовый объект
  • Постоянные хеш-карты
  • Постоянные векторы/списки
  • Постоянные наборы (включая специализированный постоянный набор int)

Ответ 5

Следуйте очень простой ориентировочной динамической прокси:

class ImmutableBuilder {

    static <T> T of(Immutable immutable) {
        Class<?> targetClass = immutable.getTargetClass();
        return (T) Proxy.newProxyInstance(targetClass.getClassLoader(),
            new Class<?>[]{targetClass},
            immutable);
    }

    public static <T> T of(Class<T> aClass) {
        return of(new Immutable(aClass, new HashMap<String, Object>()));
    }
}

class Immutable implements InvocationHandler {

    private final Class<?> targetClass;
    private final Map<String, Object> fields;

    public Immutable(Class<?> aTargetClass, Map<String, Object> immutableFields) {
        targetClass = aTargetClass;
        fields = immutableFields;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("toString")) { 
            // XXX: toString() result can be cached
            return fields.toString();
        }

        if (method.getName().equals("hashCode")) { 
            // XXX: hashCode() result can be cached
            return fields.hashCode();
        }

        // XXX: naming policy here
        String fieldName = method.getName(); 

        if (method.getReturnType().equals(targetClass)) {
          Map<String, Object> newFields = new HashMap<String, Object>(fields);
          newFields.put(fieldName, args[0]);
          return ImmutableBuilder.of(new Immutable(targetClass, newFields));
        } else {
            return fields.get(fieldName);
        }
    }

    public Class<?> getTargetClass() {
        return targetClass;
    }
}

использование:

interface Person {
    String name();
    Person name(String name);
    int age();
    Person age(int age);
}

public class Main {

    public static void main(String[] args) {
        Person mark = ImmutableBuilder.of(Person.class).name("mark").age(32);
        Person john = mark.name("john").age(24);
        System.out.println(mark);
        System.out.println(john);
    }
}

растут направления:

  • политика имен (getName, withName, name)
  • кэширование toString(), hashCode()
  • equals() реализация должна быть простой (хотя и не реализованной)

надеюсь, что это поможет:)

Ответ 6

Очень сложно, если не невозможно, сделать вещи неизменными, которые не разработаны так.

Если вы можете проектировать с нуля:

  • использовать только конечные поля
  • не ссылаются на неизменяемые объекты

Ответ 7

Вы хотите неизменность:

  • поэтому внешний код не может изменить данные?
  • поэтому после установки значения нельзя изменить?

В обоих случаях есть более простые способы достижения желаемого результата.

Отключение внешнего кода от изменения данных легко с интерфейсами:

public interface Person {
   String getName();
   Address getAddress();
}
public interface PersonImplementor extends Person {
   void setName(String name);
   void setAddress(Address address);
}

public interface Address {
    String getCity();
}


public interface AddressImplementor {
    void setCity(String city);
}

Затем, чтобы остановить изменения значения после того, как набор был также "легким", используя java.util.concurrent.atomic.AtomicReference(хотя может потребоваться изменение режима спящего режима или другого другого уровня):

class PersonImpl implements PersonImplementor {
    private AtomicReference<String> name;
    private AtomicReference<Address> address;

    public void setName(String name) {
        if ( !this.name.compareAndSet(name, name) 
           && !this.name.compareAndSet(null, name)) {
            throw new IllegalStateException("name already set to "+this.name.get()+" cannot set to "+name);
        }
    }
    // .. similar code follows....
}

Но зачем вам нужно что-то большее, чем просто интерфейсы для выполнения задачи?