Стойкие структуры данных в 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....
}
Но зачем вам нужно что-то большее, чем просто интерфейсы для выполнения задачи?
Ответ 8
В Google Guava теперь размещается множество неизменяемых/постоянных структур данных.