18

有谁知道一个库或至少有一些关于在 Java 中创建和使用持久数据结构的研究?我不将持久性称为长期存储,而是将持久性称为不变性(参见Wikipedia entry)。

我目前正在探索为持久结构建模 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();

但这仍然让人感觉有些样板。有任何想法吗?

4

8 回答 8

12

构建器会使您的代码过于冗长而无法使用。在实践中,我见过的几乎所有不可变数据结构都通过构造函数传递状态。对于它的价值,这里有一系列很好的文章,描述了 C# 中的不可变数据结构(应该很容易转换成 Java):

C# 和 Java 非常冗长,所以这些文章中的代码非常吓人。我建议学习 OCaml、F# 或 Scala,并熟悉这些语言的不变性。一旦掌握了这项技术,您就可以更轻松地将相同的编码风格应用到 Java 中。

于 2009-04-09T14:21:29.567 回答
7

我想显而易见的选择是:

o 切换到临时数据结构(构建器)进行更新。这是很正常的。StringBuilderString操纵为例。作为你的例子。

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 使用讨厌的 hack 使字段暂时可用于代码。

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

(有趣的是,我刚刚收到了 Chris Okasaki 的《纯函数式数据结构》的副本。)

于 2009-04-09T13:21:42.347 回答
6

看看函数式 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 许可下获得。

于 2009-04-09T18:15:13.143 回答
5

我用 Java 实现了一些持久性数据结构。Google 代码上的所有开源 (GPL) 代码供感兴趣的人使用:

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

到目前为止,我主要有:

  • 持久可变的测试对象
  • 持久哈希映射
  • 持久向量/列表
  • 持久集(包括专门的持久整数集)
于 2009-11-18T18:56:35.850 回答
3

使用动态代理遵循一个非常简单的尝试:

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() 实现应该很简单(尽管没有实现)

希望能帮助到你 :)

于 2009-04-10T11:24:10.283 回答
1

如果不是不可能的话,要让原本不是这样设计的东西不可变是非常困难的。

如果您可以从头开始设计:

  • 仅使用最终字段
  • 不要引用非不可变对象
于 2009-04-09T13:42:08.507 回答
0

你想要不变性:

  1. 所以外部代码不能改变数据?
  2. 所以一旦设置一个值就不能改变?

在这两种情况下,都有更简单的方法来实现所需的结果。

使用接口可以很容易地阻止外部代码更改数据:

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....
}

但是,为什么您需要的不仅仅是接口来完成任务呢?

于 2009-04-09T16:41:54.350 回答
0

Google Guava 现在托管各种不可变/持久数据结构

于 2015-04-14T10:44:22.227 回答