序列化和身份的问题在于,序列化过程实际上创建了原始对象的克隆。大多数时候,
SomeThing x = ...;
ObjectOutputStream oos = ...;
oos.writeObject(x);
其次是
ObjectInputStream ois = ...;
SomeThing y = (SomeThing)ois.readObject()
不能也不会强制执行x == y
(尽管应该是这样,但x.equals(y)
)
如果您真的想使用身份,则必须编写自定义序列化代码,这会强制从流中读取您的类的实例实际上会产生与所编写的实例相同的(如在单例中)。这很难做到,而且我认为,强迫开发人员这样做只是为了声明一个魔法密钥会使 API 很难使用。
如今,可以使用enum
s,并依靠 VM 来强制执行单例字符。
enum MyMetaDataKey implements HyptheticalMetaDataKeyInterface {
TITLE(String.class),
WIDTH(Integer.class);
private final Class<?> type;
private MyMetaDataKey(Class<?> t) { type = t; }
public Class<?> getType() { return type; }
}
缺点是,你不能声明你的枚举继承自公共基类(你可以让它实现接口),所以你必须手动编写整个支持代码,这MetaDataKey
可能会一遍又一遍地为你提供. 在上面的示例中,所有这些getType
都应该由抽象基类提供,但它不能,因为我们使用了enum
.
至于问题的第二部分......不幸的是,我觉得 C# 不够熟练来回答这个问题(除了已经提到的使用评论中已经给出的普通私有类解决方案)。
也就是说......(编辑以回答出现在 Ben 的回答的评论中的问题)实现类似事情的一种可能的解决方案(从某种意义上说,它与 Java 解决方案一样可用):
[Serializable]
public class MetaDataKey<T> {
private Guid uniqueId;
private Type type;
public MetaDataKey(Guid key, Type type) {
this.uniqueId;
this.type = type;
}
public override boolean Equals(object other) {
return other is MetaDataKey && uniqueId == ((MetaDataKey)other).uniqueId;
}
}
这可以用于
class MyStuff {
private static MetaDataKey<String> key = new MetaDataKey<String>(new Guid(), typeof(String));
}
请忽略任何违反 C# 语言的行为。我用它太久了。
这看起来像是一个有效的解决方案。然而,问题在于key
常量的初始化。如果像上面的示例那样完成,每次启动应用程序时,都会创建一个新的 Guid 值并将其用作MyStuff
的元数据值的标识符。因此,例如,如果您有一些来自程序先前调用的序列化数据(例如,存储在文件中),它将具有具有不同 Guid 值MyStuff
的元数据键的键。一个有效的,在反序列化之后,任何请求
String myData = magicDeserializedMetaDataMap.Get(MyStuff.key);
会失败——仅仅因为Guids不同。因此,为了使示例工作,您必须具有持久的预定义 Guid:
class MyStuff {
private static Guid keyId = new Guid("{pre-define-xyz}");
private static MetaDataKey<String> key = new MetaDataKey<String>(keyId, typeof(String));
}
现在,一切都如您所愿,但维护关键 Guid 的重担已经落在了您身上。我认为,这是 Java 解决方案试图通过这个可爱的匿名子类技巧来避免的。