??就個(gè)人理解而言, 序列化是對象的一種固化方式, 它實(shí)現(xiàn)了對象的持久化存儲(chǔ)(當(dāng)然實(shí)現(xiàn)方式不同,結(jié)果會(huì)有所差異).而Android存在兩種序列化的方式, 一種基于IO流的序列化, 實(shí)現(xiàn)簡單, 僅需實(shí)現(xiàn)Serializable接口而已, 可以不用添加額外的方法,源自Java; 另一種就是Android的原生實(shí)現(xiàn)方式了, 實(shí)現(xiàn)Parcelable接口了, 相比Serializable方式,實(shí)現(xiàn)起來較為復(fù)雜.下面將詳細(xì)介紹兩種實(shí)現(xiàn)方式, 以及它們被我們忽略掉的一些地方.
在GitHub工程 link 中可以找到相關(guān)代碼
Serializable
先放出它的官方文檔 : Interface Serializable
官方文檔大致解決了以下幾個(gè)問題:
-
如何序列化一個(gè)類?
通過實(shí)現(xiàn)一個(gè)Serializable接口(當(dāng)然了,繼承自Serializable的接口也算在內(nèi)), 或者繼承自一個(gè)實(shí)現(xiàn)了Seriaizable的類.
-
繼承自一個(gè)沒有實(shí)現(xiàn)Serializable接口的父類的類序列化時(shí), 應(yīng)該注意哪些事項(xiàng)?
在此種情況下, 父類必須要有一個(gè)顯示的無參構(gòu)造函數(shù),而且其無參構(gòu)造函數(shù)必須是能夠?qū)ζ渥宇惪梢姷? 比如不能使用 private 修飾, 否則會(huì)拋出異常. 其原因在于, 在反序列化的過程中, 父類的變量會(huì)通過父類的無參構(gòu)造函數(shù)進(jìn)行初始化(==按照文檔的意思, 似乎無參構(gòu)造函數(shù)必須申明為public/protected, 但我實(shí)際試了下, 不使用也可以, 只要保證子類能正常訪問就行==)
no valid constructor
-
如果序列化的對象引用了非靜態(tài)的且不能進(jìn)行序列化的對象時(shí), 在序列化時(shí)會(huì)發(fā)生什么情況以及相關(guān)的處理措施?
如果 不能進(jìn)行序列化的對象未使用 transient 關(guān)鍵詞進(jìn)行修飾, 則會(huì)拋 NotSerializableException .
這種情況下, 就需要對這個(gè)類進(jìn)行一些特殊的處理了, 需要實(shí)現(xiàn)以下三個(gè)方法.
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException
private void readObjectNoData()throws ObjectStreamException
下面分別介紹一下這幾個(gè)方法
private void writeObject(java.io.ObjectOutputStream out) throws IOException
writeObject 方法可以寫入一些無法通過默認(rèn)機(jī)制序列化的對象, 并使用 readObject 方法還原回來, 其實(shí)就是一個(gè)自定義序列化的方式. 通過執(zhí)行 out.defaultWriteObject 方法可以保存默認(rèn)參與序列化的字段, 可以使用寫入基礎(chǔ)數(shù)據(jù)類型的方法 (例如: out.writeInt)保存特殊的字段.
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException
readObject 方法實(shí)際上就是一個(gè)反序列化的方法, 通過 in.defaultReadObject 方法 恢復(fù)默認(rèn)的參與序列化的字段, 通過 類似于 in.readInt 等方式恢復(fù)特殊的字段.
private void readObjectNoData()throws ObjectStreamException
這個(gè)方法就很少用到了, 不必去過于糾結(jié), 如果有好的理解, 歡迎分享出來.從它的命名上就能看出來, 這是出現(xiàn)了序列化的流沒有給出想要的數(shù)據(jù)時(shí)出現(xiàn)的情況.根據(jù)官方文檔所述, readObjectNoData 是為了應(yīng)對 當(dāng)序列化的流沒有給出指定的被序列化的對象的父類的時(shí)候出現(xiàn)的.可能翻譯得不大準(zhǔn)確, 但大概意思就是說, 對象序列化后的流在被反序列化時(shí)發(fā)現(xiàn)對象的父類發(fā)生變動(dòng)了(這個(gè)解釋是不大準(zhǔn)確的).這種情況出現(xiàn)在 接收部分使用了與發(fā)送部分不同版本的反序列化實(shí)例的類, 并且接收版本的類并沒有被發(fā)送版本所繼承;或者是序列化流被篡改了. 因此, readObjectNoData 適用于初始化反序列化對象, 盡管流不完整或者不那么對頭
為什么說上述的理解是有問題的呢?原因在于, 我無法根據(jù)以上描述來實(shí)現(xiàn)調(diào)用 readObjectNoData 的場景. 我在 stackoverflow 上找到了一個(gè)相關(guān)的場景, 并大致整理了一個(gè)例子, 由于比較難以搞清楚, 所以貼出源碼.
序列化時(shí)的代碼
package th.algorithm.practice;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.HashMap;
/**
* Created by me_touch on 18-4-12.
*
*/
public class SerializableTest implements Serializable{
private B b;
public SerializableTest(){
this.b = new B();
}
public void setB(int value) {
this.b.setB(value);
this.b.setA(value + 1);
System.out.println(b.getB() + b.getA());
}
public void trySerialB(){
try {
File file = new File("algorithm/B.tmp");
if(file.exists())
file.delete();
file.createNewFile();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(b);
oos.flush();
oos.close();
}catch (Exception e){
e.printStackTrace();
}
}
public void tryDeSerialB(){
try {
File file = new File("algorithm/B.tmp");
if(file.exists()) {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
B b = (B)ois.readObject();
//System.out.println(b.getB() + b.getC());
}
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args){
SerializableTest test = new SerializableTest();
test.setB(5);
test.trySerialB();
}
public static class B extends A implements Serializable{
private int b;
public B(){
}
public B(int a){
}
public void setB(int b) {
this.b = b;
}
public int getB() {
return b;
}
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
in.defaultReadObject();
}
}
public static class A{
private int a;
/**
* 若想子類可序列化, 則必須存在子類可訪問的無參構(gòu)造函數(shù)
*/
A(){
this.a = 0;
}
/**
* 不能使用 private 修飾, 對子類不可訪問
private A(){
}
*/
public A(int a){
this.a = a;
}
public void setA(int a) {
this.a = a;
}
public int getA() {
return a;
}
}
}
現(xiàn)在我們變更類A, 并直接進(jìn)行反序列化操作, 修改過后的代碼如下
package th.algorithm.practice;
import java.io.File;
import java.io.FilereadObjectNoData has been invoked
13InputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.HashMap;
/**
* Created by me_touch on 18-4-12.
*
*/
public class SerializableTest implements Serializable{
private B b;
public SerializableTest(){
this.b = new B();
}
public void setB(int value) {
this.b.setB(value);
this.b.setA(value + 1);
System.out.println(b.getB() + b.getA());
}
public void trySerialB(){
try {
File file = new File("algorithm/B.tmp");
if(file.exists())
file.delete();
file.createNewFile();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(b);
oos.flush();
oos.close();
}catch (Exception e){
e.printStackTrace();
}
}
public void tryDeSerialB(){
try {
File file = new File("algorithm/B.tmp");
if(file.exists()) {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
B b = (B)ois.readObject();
System.out.println(b.getB() + b.getC());
}
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args){
SerializableTest test = new SerializableTest();
test.tryDeSerialB();
}
public static class B extends A implements Serializable{
private int b;
public B(){
}
public B(int a){
}
public void setB(int b) {
this.b = b;
}
public int getB() {
return b;
}
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
in.defaultReadObject();
}
}
public static class A implements Serializable{
private int a;
private int c;
/**
* 若想子類可序列化, 則必須存在子類可訪問的無參構(gòu)造函數(shù)
*/
A(){
this.a = 0;
}
/**
* 不能使用 private 修飾, 對子類不可訪問
private A(){
}
*/
public A(int a){
this.a = a;
}
public void setA(int a) {
this.a = a;
}
public int getA() {
return a;
}
public void setC(int c) {
this.c = c;
}
public int getC() {
return c;
}
private void readObjectNoData()throws ObjectStreamException{
setA(6);
setC(8);
System.out.println("readObjectNoData has been invoked");
}
}
}
執(zhí)行結(jié)果
readObjectNoData has been invoked
13
以上就是關(guān)于這三個(gè)方法的大致介紹, 自定義序列化的過程中 readObjectNoData 不一定需要自己去實(shí)現(xiàn), 而 writeObject 和 readObject 方法則是需要去實(shí)現(xiàn)的.
-
如何使用特定的對象來替換序列化或者反序列化后的對象
需要根據(jù)需求實(shí)現(xiàn)下列方法
/**
* 序列化時(shí)替換
* 即可以使用private, protected 等權(quán)限關(guān)鍵詞(子類的訪問權(quán)限 follow java 本身的規(guī)則)
*/
ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
/**
* 反序列化時(shí)替換
* 可以用來解決單例被反序列化時(shí)帶來的安全問題(即單例不再是單例)
* 即可以使用private, protected 等權(quán)限關(guān)鍵詞(子類的訪問權(quán)限 follow java 本身的規(guī)則)
*/
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
如果在序列化的過程中使用了 writeReplace 方法, 則反序列化的過程會(huì)調(diào)用用作替代 object 的反序列化方式, 而不是原 object 的序列化方式.
父類在序列化,或者反序列化的過程中, 使用了這兩個(gè)中對應(yīng)的方法, 則子類也會(huì)在序列化或者反序列的過程中使用這兩個(gè)中對應(yīng)的方法.
package th.algorithm.serial;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* Created by me_touch on 18-4-18.
*
*/
public class SerializableReplaceTest {
private void trySerialInstance(){
try {
System.out.println(SingleInstance.INSTANCE);
File file = new File("algorithm/files/instance.tmp");
if(file.exists())
file.delete();
file.createNewFile();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(SingleInstance.INSTANCE);
oos.flush();
oos.close();
}catch (Exception e){
e.printStackTrace();
}
}
private void tryDeSerialInstance(){
try {
File file = new File("algorithm/files/instance.tmp");
if(file.exists()) {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Object object = ois.readObject();
System.out.println(object);
}
}catch (Exception e){
e.printStackTrace();
}
}
private void trySerialChild(){
Child replace = new Child();
replace.setA(9);
try {
File file = new File("algorithm/files/child.tmp");
if(file.exists())
file.delete();
file.createNewFile();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(replace);
oos.flush();
oos.close();
}catch (Exception e){
e.printStackTrace();
}
}
private void tryDeSerialChild(){
try {
File file = new File("algorithm/files/c.tmp");
if(file.exists()) {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Object object = ois.readObject();
System.out.println(object);
}
}catch (Exception e){
e.printStackTrace();
}
}
private void trySerial(){
WriteReplace replace = new WriteReplace();
replace.setA(9);
try {
File file = new File("algorithm/files/c.tmp");
if(file.exists())
file.delete();
file.createNewFile();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(replace);
oos.flush();
oos.close();
}catch (Exception e){
e.printStackTrace();
}
}
private void tryDeSerial(){
try {
File file = new File("algorithm/files/c.tmp");
if(file.exists()) {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Object object = ois.readObject();
System.out.println(object);
}
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args){
SerializableReplaceTest test = new SerializableReplaceTest();
test.trySerial();
test.tryDeSerial();
test.trySerialChild();
test.tryDeSerialChild();
test.trySerialInstance();
test.tryDeSerialInstance();
}
public static class Child extends WriteReplace{
}
public static class WriteReplace implements Serializable{
private int a;
public void setA(int a) {
this.a = a;
}
public int getA() {
return a;
}
private Object writeReplace() throws ObjectStreamException{
return new Common();
}
}
public static class Common implements Serializable{
private transient int a;
public Common(){
this.a = 1;
}
public int getA() {
return a;
}
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(a);
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
in.defaultReadObject();
in.readInt();
System.out.println("Common readObject has been invoked");
}
}
public static class SingleInstance implements Serializable{
public static SingleInstance INSTANCE = new SingleInstance();
private transient int a;
private SingleInstance(){
this.a = 1;
}
public int getA() {
return a;
}
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeInt(a);
}
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
in.defaultReadObject();
in.readInt();
System.out.println("SingleInstance readObject has been invoked");
}
private Object readResolve() throws ObjectStreamException{
return INSTANCE;
}
}
}
運(yùn)行結(jié)果
Common readObject has been invoked
th.algorithm.serial.SerializableReplaceTest$Common@27d6c5e0
Common readObject has been invoked
th.algorithm.serial.SerializableReplaceTest$Common@3feba861
th.algorithm.serial.SerializableReplaceTest$SingleInstance@5b480cf9
SingleInstance readObject has been invoked
th.algorithm.serial.SerializableReplaceTest$SingleInstance@5b480cf9
-
如何理解 serialVersionUID
serialVersionUID 既可以自動(dòng)創(chuàng)建, 也可以手動(dòng)賦值.其目的在于判斷序列化和反序化的對象對應(yīng)的類是否兼容.如果對應(yīng)的serialVersionUID不同, 則在反序列化的過程中拋出 InvalidClassException.
通過在序列化類中以如下方式定義 field , 可以手動(dòng)指定 serialVersionUID
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
需要注意的是, 由于自動(dòng)計(jì)算 serialVersionUID 的方式對類的變化高度敏感, 甚至某種程度上會(huì)依賴于 Java compiler, 所以手動(dòng)指定一個(gè) serialVersionUID 是非常有必要的.并且盡可能定義為 private .畢竟這不是一個(gè)能被子類繼承的成員.對于 Array classes 而言, 就放棄治療吧, 無法手動(dòng)指定, 數(shù)組類存在默認(rèn)計(jì)算的 serialVersionUID, 雖然它們并不會(huì)在匹配的時(shí)候用到.
按照注釋操作, 可以獲取到異常
package th.algorithm.serial;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* ===================================================
* 首先使用現(xiàn)有代碼生成一個(gè)序列化后的文件
* 然后恢復(fù)注釋掉的部分(忽略掉手動(dòng)指定uid的那部分代碼)
* , 并注釋掉 main函數(shù)中
* 序列化的部分, 并保留反序列化的部分, 運(yùn)行.
* ====================================================
*/
public class SerializableUidTest {
private void trySerial(){
try {
File file = new File("algorithm/files/uid.tmp");
if(file.exists())
file.delete();
file.createNewFile();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(new Uid());
oos.flush();
oos.close();
}catch (Exception e){
e.printStackTrace();
}
}
private void tryDeSerial(){
try {
File file = new File("algorithm/files/uid.tmp");
if(file.exists()) {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Object object = ois.readObject();
System.out.println(object);
}
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args){
SerializableUidTest test = new SerializableUidTest();
// test.trySerial();
test.tryDeSerial();
}
public static class Uid implements Serializable{
// private static final long serialVersionUID = 42L;
private int a;
private int c;
public Uid(){
this.a = 6;
}
}
}
反序列化后的異常
java.io.InvalidClassException: th.algorithm.serial.SerializableUidTest$Uid; local class incompatible: stream classdesc serialVersionUID = 4997188412835492989, local class serialVersionUID = 7081360090031566712
可以看出, UID發(fā)生了明顯的變化.若手動(dòng)指定UId, 重復(fù)上述操作, 則不會(huì)出現(xiàn)異常.