Java 8 和 Java 11 之间的不同反序列化行为
我在Java 11中反序列化时遇到了一个问题,导致一个找不到的密钥。如果任何对这个问题有更多了解的人都可以说我提出的解决方法看起来还不错,或者我是否可以做一些更好的事情,我将不胜感激。HashMap
考虑以下人为的实现(实际问题中的关系有点复杂且难以更改):
public class Element implements Serializable {
private static long serialVersionUID = 1L;
private final int id;
private final Map<Element, Integer> idFromElement = new HashMap<>();
public Element(int id) {
this.id = id;
}
public void addAll(Collection<Element> elements) {
elements.forEach(e -> idFromElement.put(e, e.id));
}
public Integer idFrom(Element element) {
return idFromElement.get(element);
}
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Element)) {
return false;
}
Element other = (Element) obj;
return this.id == other.id;
}
}
然后,我创建一个具有对自身引用的实例,并对其进行序列化和反序列化:
public static void main(String[] args) {
List<Element> elements = Arrays.asList(new Element(111), new Element(222));
Element originalElement = elements.get(1);
originalElement.addAll(elements);
Storage<Element> storage = new Storage<>();
storage.serialize(originalElement);
Element retrievedElement = storage.deserialize();
if (retrievedElement.idFrom(retrievedElement) == 222) {
System.out.println("ok");
}
}
如果我在Java 8中运行此代码,则结果为“ok”,如果我在Java 11中运行它,则结果是a,因为返回.NullPointerException
retrievedElement.idFrom(retrievedElement)
null
我放了一个断点,注意到:HashMap.hash()
- 在Java 8中,当被反序列化并被添加到其中时,它是222,所以我以后能够找到它。
idFromElement
Element(222)
id
- 在Java 11中,没有初始化(0代表或null,如果我把它变成一个),所以当它存储在.后来,当我尝试检索它时,是222,所以返回。
id
int
Integer
hash()
HashMap
id
idFromElement.get(element)
null
我知道这里的序列是 deserialize(Element(222)) -> deserialize(idFromElement) ->未完成的元素(222) 放入 Map 中。但是,由于某种原因,在Java中,当我们到达最后一步时,8已经初始化,而在Java 11中则不是。id
我想出的解决方案是进行瞬态并编写自定义和方法来强制在以下之后进行反序列化:idFromElement
writeObject
readObject
idFromElement
id
...
transient private Map<Element, Integer> idFromElement = new HashMap<>();
...
private void writeObject(ObjectOutputStream output) throws IOException {
output.defaultWriteObject();
output.writeObject(idFromElement);
}
@SuppressWarnings("unchecked")
private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException {
input.defaultReadObject();
idFromElement = (HashMap<Element, Integer>) input.readObject();
}
在序列化/反序列化期间,我能够找到的关于顺序的唯一参考是:
对于可序列化的类,将设置 SC_SERIALIZABLE 标志,字段数计算可序列化字段的数量,后跟每个可序列化字段的描述符。描述符按规范顺序编写。基元类型化字段的描述符首先按字段名称排序,然后按字段名称排序的对象类型化字段的描述符编写。这些名称使用 String.compareTo 进行排序。
这在Java 8和Java 11文档中是相同的,并且似乎暗示应该首先编写原始类型字段,所以我预计不会有任何区别。
为完整性,包括的实施:Storage<T>
public class Storage<T> {
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
public void serialize(T object) {
buffer.reset();
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(buffer)) {
objectOutputStream.writeObject(object);
objectOutputStream.flush();
} catch (Exception ioe) {
ioe.printStackTrace();
}
}
@SuppressWarnings("unchecked")
public T deserialize() {
ByteArrayInputStream byteArrayIS = new ByteArrayInputStream(buffer.toByteArray());
try (ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayIS)) {
return (T) objectInputStream.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}