简体   繁体   English

为其他对象实现String.intern()的等效项

[英]Implementing an equivalent to String.intern() for other objects

I'm trying to implement an equivalent to String.intern(), but for other objets. 我正在尝试实现String.intern()的等价物,但对于其他objets。 My goal is the following: I've an object A which I will serialize and then deserialize. 我的目标如下:我有一个对象A,我将序列化然后反序列化。 If there is another reference to A somewhere, I want the result of the deserialization to be the same reference. 如果在某处有另一个对A的引用,我希望反序列化的结果是相同的引用。

Here is one example of what I would expect. 这是我期望的一个例子。

MyObject A = new MyObject();
A.data1 = 1;
A.data2 = 2;
byte[] serialized = serialize(A);
A.data1 = 3;
MyObject B = deserialize(serialized); // B!=A and B.data1=1, B.data2=2
MyObject C = B.intern(); // Here we should have C == A. Consequently C.data1=3 AND C.data2=2

Here is my implementation atm. 这是我的实现atm。 (the MyObject class extends InternableObject ) MyObject类扩展了InternableObject

public abstract class InternableObject {

private static final AtomicLong maxObjectId = new AtomicLong();
private static final Map<Long, InternableObject> dataMap = new ConcurrentHashMap<>();
private final long objectId;

public InternableObject() {
    this.objectId = maxObjectId.incrementAndGet();

    dataMap.put(this.objectId, this);
}

@Override
protected void finalize() throws Throwable {
    super.finalize();
    dataMap.remove(this.objectId);
}

public final InternableObject intern() {
    return intern(this);
}

public static InternableObject intern(InternableObject o) {
    InternableObject r = dataMap.get(o.objectId);

    if (r == null) {
        throw new IllegalStateException();
    } else {
        return r;
    }
}
}

My unit test (which fails): 我的单元测试(失败):

    private static class MyData extends InternableObject implements Serializable {

    public int data;

    public MyData(int data) {
        this.data = data;
    }
}

@Test
public void testIntern() throws Exception {
    MyData data1 = new MyData(7);

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(data1);
    oos.flush();
    baos.flush();
    oos.close();
    baos.close();
    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bais);
    MyData data2 = (MyData) ois.readObject();

    Assert.assertTrue(data1 == data2.intern()); // Fails here
}

The failure is due to the fact that, when deserializing, the constructor of InternableObject is called, and thus objectId will be 2 (even if the serialized data contains "1") 失败的原因是,在反序列化时,会调用InternableObject的构造函数,因此objectId将为2(即使序列化数据包含“1”)

Any idea about how to solve this particular problem or, another approach to handle the high level problem ? 有关如何解决这个特定问题的任何想法,或另一种处理高级问题的方法?

Thanks guys 多谢你们

Do not use the constructor to create instances. 不要使用构造函数来创建实例。 Use a factory method that checks if an instance already exists first, only create an instance if there isn't already a matching one. 使用工厂方法检查实例是否已经存在,只创建一个实例(如果还没有匹配的实例)。

To get serialization to cooperate, your class will need to make use of readResolve() / writeReplace(). 要使序列化合作,您的类将需要使用readResolve()/ writeReplace()。 http://docs.oracle.com/javase/7/docs/platform/serialization/spec/serial-arch.html#4539 http://docs.oracle.com/javase/7/docs/platform/serialization/spec/serial-arch.html#4539

The way you implemented your constructor, you're leaking a reference during construction, which can lead to very hard to nail down problems. 你实现构造函数的方式,你在构造过程中泄漏了一个引用,这可能导致很难解决问题。 Also, your instance map isn't protected by any locks, so its not thread save. 此外,您的实例映射不受任何锁保护,因此它不是线程保存。

Typically intern() forms an aspect , and maybe should not be realized as a base class, maybe too restricting its usage in a more complex constellation. 通常, intern()形成一个方面 ,并且可能不应该被实现为基类,可能过于限制其在更复杂的星座中的使用。

There are two aspects: 有两个方面:

1. Sharing the "same" object. 1.共享“相同”对象。

Internalizing an object only gives a profit, when several objects can be "internalized" to the same object. 当多个对象可以“内化”到同一个对象时,内化对象只会带来利润。 So I think, that InternalableObjecte. 所以我认为,那是InternalableObjecte。 with a new sequential number is not really adequate. 使用新的序号并不合适。 More important is that the class defines a fitting equals and hashCode. 更重要的是,类定义了一个fit equals和hashCode。

Then you can do an identity Map<Object, Object> : 然后你可以做一个身份Map<Object, Object>

public class InternMap {
    private final Map<Object, Object> identityMap = new HashMap<>();
    public static <I extends Internalizable<?>> Object intern(I x) {
        Object first = identityMap.get(x);
        if (first == null) {
           first = x;
           identityMap.put(x, x);
        }
        return first;
    }
}

InternMap could be used for any class, but above we restrict it to Internalizable things. InternMap可用于任何类,但在上面我们将其限制为Internalizable事物。

2. Replacing a dynamically created non-shared object with it's .intern() . 2.用它的.intern()替换动态创建的非共享对象。

Which in Java 8 could be realised with a defualt method in an interface: 在Java 8中可以使用接口中的defualt方法实现:

interface Internalizable<T> {
    public static final InternMap interns = new InternMap();
    public default T intern(Class<T> klazz) {
        return klazz.cast(internMap.intern(this));
    }

class C implements Internalizable<C> { ... }

C x = new C();
x = x.intern(C.class);

The Class<T> parameter needed because of type erasure. 由于类型擦除,需要Class<T>参数。 Concurrency disregarded here. 并发在这里被忽略了。

Prior to Java 8, just use an empty interface Internalizable as _marker: interface, and use a static InternMap. 在Java 8之前,只需使用一个空接口Internalizable作为_marker:interface,并使用静态InternMap。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM