1.serialVersionUID的用途?
java推薦實現(xiàn)Serializable接口的類都定義這個常量,其用途是用來標示一個類都版本信息。當一個對象被序列化成字節(jié)流,之后再反序列化時,可能反序列化的類已經(jīng)被修改過,例如增加或刪除了幾個字段等等,這時候,如果修改前和修改后的類沒有相同的serialVersionUid, java就認為類版本發(fā)生了變化,不再將其與序列化時的類當成同一個類,因此會報ClassNotFound異常。聲明SerialVersionUid的目的就是告訴java,雖然類修改類,但依然可以反序列化,從序列化角度看,類其實并沒有修改。
2. serialVersionUid默認如何生成?
如果沒有顯示聲明該字段,java會第一序列化這個類時,生成一個默認該字段,那么它的值是根據(jù)什么生產(chǎn)的呢?生成默認serialVersionUid時,考慮的因素非常多,幾乎涵蓋一個Class的所有方面:
- 類名: 包括包名和類名
- 類修飾符:如public, final, abstract, interface等等
- 類所實現(xiàn)的所有接口名稱(非數(shù)組)
- 所有declaredFields: 以字段名稱排序,生成serialVersionUid時要包含字段name, signature, modifier
- 所有構(gòu)造函數(shù),以signature排序,生成時包含signature和modifier
6.所有declaredMethods, 以name和signature排序,生成時包含name, signature, modifier
最后匯總以上6個方面,經(jīng)過SHA編碼就生成了默認servialVersionUid.
可以看到,除了字段/method/constructor出現(xiàn)的順序外,幾乎任何類元素的改變,都會導致serialVersionUid的改變,這樣是為啥一定要顯示聲明serialVersionUid的原因。
完整算法參考:java.io.ObjectStreamClass.computeDefaultSUID(Class<?> clazz)
反序列化時對象如何創(chuàng)建?
序列化一個對象時,會將其Class信息寫入字節(jié)流中,反序列化時可以獲取到其Class信息,直觀上想,應(yīng)該是以下步驟:
- 通過Class信息創(chuàng)建一個對象
- 執(zhí)行初始化語句為field賦值
- 調(diào)用構(gòu)造函數(shù)進行初始化
- 將字節(jié)流中的字段數(shù)據(jù)通過反射賦值給新對象的各個字段
其中1~3步都是通過Constructor.newInstance完成,第4步是普通第反射賦值。
看起來很容易理解,跟一般的ORM邏輯一樣,然而這里有個問題,就是第三步,如果有多個構(gòu)造函數(shù)會調(diào)哪個,如果構(gòu)造函數(shù)有參數(shù)怎么辦?因此上面的步驟是說不通的,實際的反序列化過程也不是這樣的,而是:
- 通過Class信息創(chuàng)建對象
- 遞歸查找祖先類,找到第一個"沒有"實現(xiàn)Serializable接口的祖先類,并獲取其"無參"構(gòu)造函數(shù)(由于所有類都是Object子類,因此一定有這樣的構(gòu)造函數(shù), 另外需要注意modifier訪問權(quán)限是有效的), 使用構(gòu)造函數(shù)對剛創(chuàng)建對象進行初始化
- 通過Unsafe類的putXxx方法為字段賦值:注意不是通過反射,反射沒辦法對final字段賦值,而Unsafe確可以,因此final字段反序列化跟普通字段就一樣了。
綜上可知,反序列化時并不會調(diào)用目標類的構(gòu)造函數(shù),也不會執(zhí)行初始化代碼塊,同樣也不會執(zhí)行字段聲明時的賦值語句。考慮如下代碼:
V1:
public class TestSerializable implements Serializable {
private static final long serialVersionUID = 1L;
private int field1 = 10;
private String field2 = "hello";
}
接下來增加一個字段, 并增加一個構(gòu)造函數(shù)
V2:
public class TestSerializable implements Serializable {
private static final long serialVersionUID = 1L;
private int field1 = 10;
private String field2 = "hello";
private String field3 = "world";
public TestSerializable() {
field2="hello 2";
}
}
現(xiàn)在序列化一個V1版本的對象,并反序列化成V2版本的對象object,想想object.field3值是什么?是"world"嗎?不是! 是null!, 從上面的反序列化過程可以看出,反序列化不會執(zhí)行構(gòu)造函數(shù),及初始化語句,并且反序列化賦值的時候,字節(jié)流中并沒有這個字段,所以結(jié)果是null。所以通過反序列化創(chuàng)建對象時,需要特別注意這些可能為null的字段。
再看看field2的值會是多少? 答案是"hello",而不是"hello 2", 原因同上,構(gòu)造函數(shù)不會執(zhí)行。
什么樣的類實例可以序列化?
- 數(shù)組(數(shù)組元素必需都是可序列化的)
- Class
- String
- Enum類型
- 實現(xiàn)了Serializable接口的類:
并非實現(xiàn)了Serializable接口的類都是可序列化的,有兩種情況: - 自定義了writeObject(java.io.ObjectOutputStream)和readObject(java.io.ObjectInputStream)方法。(注意這兩個方法必須成對出現(xiàn),且字段的寫入和讀入順序必須嚴格一致),這種情況下寫入字節(jié)流的除了類描述信息外,就只有writeObject中寫入的字段值(不會再寫入字段名字等信息), 因此讀出來的時候要精確了解寫入的數(shù)據(jù)。
- 沒有自定writeObject和readObject, 則所有字段都必須是可序列化的