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, ie 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();
但这仍然有点像锅炉板。 有任何想法吗?
我想明显的选择是:
o切换到临时数据结构(构建器)以进行更新。 这很正常。 例如,用于String
操作的StringBuilder
。 作为你的榜样。
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() ), px(), py(), pz() );
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 px(); } public Yyy y() { return py(); } public Zzz z() { return pz(); } });
o使用令人讨厌的黑客来使字段暂时可用于代码。
final Person p3 = new PersonExploder(p) {{ a = new AddressExploder(a) {{ city = "Berlin"; }}.get(); }}.get();
(有趣的是,我刚刚放下了Chris Okasaki的Purely Functional Data Structures副本。)
构建器将使您的代码过于冗长而无法使用。 在实践中,我见过的几乎所有不可变数据结构都通过构造函数传递状态。 值得一提的是,这里有一系列很好的post描述了C#中的不可变数据结构(它应该很容易转换成Java):
- 第1部分:不变性的种类
- 第2部分:简单不可变堆栈
- 第3部分:协变不可变堆栈
- 第4部分:不可变队列
- 第5部分:Lolz! (包括为完整性)
- 第6部分:简单二叉树
- 第7部分:关于二叉树的更多信息
- 第8部分:关于二叉树的更多信息
- 第9部分:AVL树实现
- 第10部分:双端队列
- 第11部分:工作双端队列实现
C#和Java非常冗长,因此这些文章中的代码非常可怕。 我建议学习OCaml,F#或Scala,并熟悉这些语言的不变性。 掌握了这项技术后,您将能够更轻松地将相同的编码风格应用于Java。
看看Functional Java 。 目前提供的持久性数据结构包括:
- 单链表(fj.data.List)
- 懒惰的单链表(fj.data.Stream)
- 非空列表(fj.data.NonEmptyList)
- 可选值(长度为0或1的容器)(fj.data.Option)
- 设置(fj.data.Set)
- 多路树(又名玫瑰树)(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)
许多用法示例与二进制分发一起提供。 该来源可通过Google Code的BSD许可获得。
我在Java中实现了一些持久性数据结构。 Google代码上的所有开源(GPL)适用于任何感兴趣的人:
http://code.google.com/p/mikeralib/source/browse/#svn/trunk/Mikera/src/mikera/persistent
我到目前为止的主要是:
- 持久可变测试对象
- 持久的哈希映射
- 持久向量/列表
- 持久集(包括一组专门的持久性int)
使用动态代理跟随一个非常简单的暂定:
class ImmutableBuilder { static T of(Immutable immutable) { Class> targetClass = immutable.getTargetClass(); return (T) Proxy.newProxyInstance(targetClass.getClassLoader(), new Class>[]{targetClass}, immutable); } public static T of(Class aClass) { return of(new Immutable(aClass, new HashMap())); } } class Immutable implements InvocationHandler { private final Class> targetClass; private final Map fields; public Immutable(Class> aTargetClass, Map 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 newFields = new HashMap(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()实现应该是直截了当的(虽然没有实现)
希望能帮助到你 :)
如果不是不可能的话,使事情变得不可变是非常困难的。
如果你可以从头开始设计:
- 仅使用最终字段
- 不要引用非不可变对象
你想要不变性吗?
- 所以外部代码不能改变数据?
- 那么一旦设定值无法改变?
在这两种情况下,都有更简单的方法来实现所需的结果。
使用接口可以轻松停止外部代码更改数据:
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(虽然可能需要修改hibernate或其他一些持久层使用),一旦设置也很容易停止对值的更改:
class PersonImpl implements PersonImplementor { private AtomicReference name; private AtomicReference 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.... }
但为什么你需要的不仅仅是接口来完成任务?
Google Guava现在托管了各种不可变/持久数据结构 。