五年後,我發現我原來的(但非空!)在我通過谷歌偶然發現這篇文章後,回答不令人滿意。另一種解決方案是根本不使用反射,並使用Boann提出的技術。
它還利用GetField類返回的ObjectInputStream#readFields()
方法,該方法根據序列化規範必須在私有方法readObject(...)
中調用。
該解決方案通過將取回的字段存儲在由反序列化過程創建的臨時「實例」的臨時瞬態字段(稱爲FinalExample#fields
)中,使得字段反序列化變得明確。所有對象字段然後反序列化並調用readResolve(...)
:創建一個新實例,但這次使用構造函數,放棄臨時字段的臨時實例。該實例使用GetField
實例明確地恢復每個字段;與其他構造函數一樣,這是檢查任何參數的地方。如果構造函數拋出異常,則將其轉換爲InvalidObjectException
,並且此對象的反序列化失敗。
包含的微基準測試確保此解決方案不會比默認序列化/反序列化慢。事實上,這是我的電腦上:
Problem: 8.598s Solution: 7.818s
那麼這裏就是代碼:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.ObjectInputStream.GetField;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import org.junit.Test;
import static org.junit.Assert.*;
public class FinalSerialization {
/**
* Using default serialization, there are problems with transient final
* fields. This is because internally, ObjectInputStream uses the Unsafe
* class to create an "instance", without calling a constructor.
*/
@Test
public void problem() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
WrongExample x = new WrongExample(1234);
oos.writeObject(x);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
WrongExample y = (WrongExample) ois.readObject();
assertTrue(y.value == 1234);
// Problem:
assertFalse(y.ref != null);
ois.close();
baos.close();
bais.close();
}
/**
* Use the readResolve method to construct a new object with the correct
* finals initialized. Because we now call the constructor explicitly, all
* finals are properly set up.
*/
@Test
public void solution() throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
FinalExample x = new FinalExample(1234);
oos.writeObject(x);
oos.close();
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
FinalExample y = (FinalExample) ois.readObject();
assertTrue(y.ref != null);
assertTrue(y.value == 1234);
ois.close();
baos.close();
bais.close();
}
/**
* The solution <em>should not</em> have worse execution time than built-in
* deserialization.
*/
@Test
public void benchmark() throws Exception {
int TRIALS = 500_000;
long a = System.currentTimeMillis();
for (int i = 0; i < TRIALS; i++) {
problem();
}
a = System.currentTimeMillis() - a;
long b = System.currentTimeMillis();
for (int i = 0; i < TRIALS; i++) {
solution();
}
b = System.currentTimeMillis() - b;
System.out.println("Problem: " + a/1000f + "s Solution: " + b/1000f + "s");
assertTrue(b <= a);
}
public static class FinalExample implements Serializable {
private static final long serialVersionUID = 4772085863429354018L;
public final transient Object ref = new Object();
public final int value;
private transient GetField fields;
public FinalExample(int value) {
this.value = value;
}
private FinalExample(GetField fields) throws IOException {
// assign fields
value = fields.get("value", 0);
}
private void readObject(ObjectInputStream stream) throws IOException,
ClassNotFoundException {
fields = stream.readFields();
}
private Object readResolve() throws ObjectStreamException {
try {
return new FinalExample(fields);
} catch (IOException ex) {
throw new InvalidObjectException(ex.getMessage());
}
}
}
public static class WrongExample implements Serializable {
private static final long serialVersionUID = 4772085863429354018L;
public final transient Object ref = new Object();
public final int value;
public WrongExample(int value) {
this.value = value;
}
}
}
一個值得注意的問題:每當類指的是另一個對象實例,有可能泄漏臨時由序列化過程創建的「實例」:只有在讀取所有子對象後纔會發生對象解析,因此子對象可能會保留對臨時對象的引用。類可以通過檢查GetField
臨時字段爲空來檢查這種非法構造的實例的使用。只有當它爲空時,它纔是使用常規構造函數創建的,而不是通過反序列化過程。
給自己的提示:或許五年後會有更好的解決方案。回頭見!
謝謝。我懷疑這也是,但不知道我沒有錯過任何東西。 – doublep 2010-06-03 19:23:44
你的回答「瞬變不能是最終的」是不正確的:請詳細解釋Hibernate源代碼和最終瞬態:https://github.com/hibernate/hibernate-orm/blob/4.3.7.Final/hibernate- core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java – 2014-12-04 11:08:16
其實答案是錯誤的。 'transient'字段可以是'final'。但是爲了使其他的工作不是默認值('false' /'0' /'0.0' /'null'),你不僅需要實現'readObject()'而且還要實現'readResolve()',或使用* Reflection *。 – 2015-02-26 10:51:54