Năm năm sau, tôi thấy ban đầu của tôi (nhưng không null!) trả lời không thỏa đáng sau khi tôi tình cờ gặp bài đăng này qua Google. Một giải pháp khác sẽ được sử dụng không có sự phản ánh nào cả, và sử dụng kỹ thuật được Boann đề xuất.
Nó cũng sử dụng lớp GetField được trả về theo phương pháp ObjectInputStream#readFields()
, theo đặc điểm kỹ thuật tuần tự hóa phải được gọi theo phương thức riêng readObject(...)
.
Giải pháp làm cho trường deserialization rõ ràng bằng cách lưu trữ các trường được truy xuất trong trường tạm thời tạm thời (được gọi là FinalExample#fields
) của một trường hợp tạm thời được tạo bởi quá trình deserialization. Tất cả các trường đối tượng sau đó được deserialized và readResolve(...)
được gọi là: một cá thể mới được tạo ra nhưng lần này sử dụng một hàm tạo, loại bỏ cá thể tạm thời bằng trường tạm thời. Ví dụ này phục hồi một cách rõ ràng từng trường bằng cách sử dụng cá thể GetField
; đây là nơi để kiểm tra bất kỳ tham số nào như bất kỳ hàm tạo nào khác. Nếu một ngoại lệ được ném bởi constructor, nó được dịch sang một số InvalidObjectException
và deserialization của đối tượng này thất bại.
Phạm vi vi chuẩn bao gồm đảm bảo rằng giải pháp này không chậm hơn serialization/deserialization mặc định. Trên thực tế, nó là trên máy tính của tôi:
Problem: 8.598s Solution: 7.818s
Sau đó, ở đây là các mã:
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;
}
}
}
Một lưu ý thận trọng: bất cứ khi nào lớp đề cập đến một trường hợp đối tượng, nó có thể là có thể bị rò rỉ tạm thời "instance" được tạo bởi quá trình tuần tự hóa: độ phân giải đối tượng chỉ xảy ra sau khi tất cả các đối tượng phụ được đọc, do đó có thể cho các đối tượng con để giữ tham chiếu đến đối tượng tạm thời. Các lớp học có thể kiểm tra việc sử dụng các trường hợp được xây dựng bất hợp pháp như vậy bằng cách kiểm tra rằng trường tạm thời GetField
là không. Chỉ khi nó là null, nó được tạo ra bằng cách sử dụng một hàm tạo thông thường và không thông qua quá trình deserialization.
Lưu ý tự: Có lẽ giải pháp tốt hơn tồn tại sau năm năm. Gặp lại sau!
Cảm ơn. Tôi nghi ngờ đó là cách đó, nhưng không chắc tôi đã không bỏ lỡ một cái gì đó. – doublep
Câu trả lời của bạn "transients không thể là final" là không chính xác: hãy giải thích mã nguồn Hibernate với 'final transient' trên tất cả: https://github.com/hibernate/hibernate-orm/blob/4.3.7.Final/hibernate- core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java –
Thực ra câu trả lời là sai. Các trường 'transient' có thể là' final'. Nhưng để làm việc đó với giá trị mặc định khác ('false' /' 0'/'0.0' /' null'), bạn muốn thực hiện không chỉ 'readObject()' mà còn 'readResolve()', hoặc sử dụng * Reflection *. –