场景,有一件礼物Gift(对象)我要把它送给远在国外的女票,怎么办?只能把这件礼物放在包裹里邮寄,女票收到之后在打开包裹得到这个礼物。那么我的理解Serializeable就是干这件事的。
Serializable是Java里面所提供的一个序列化接口,它只是一个空接口,内部并没有任何方法,是一个典型的标识接口(注1)。为我们提供了标准的序列化与反序列化操作。Serializable的使用非常简单,只要把想实现序列化的对象Gift 实现该接口,并添加下面一行代码就可以private static final long serialVersionUID=8712312312312321312L。其实serialVersoinUID也不是必须的,如果没有手动添加,那么也会自动生成一个。不过会对反序列化产生影响。下面我们看一个Gift实例:
public class Gift implements Serializable { public static final long serialVersionUID = 8098080809810941L; public String name; public int size; public float price; public Gift(String name, int size, float price) { this.name = name; this.size = size; this.price = price; } @Override public String toString() { return "Gift{" + "name='" + name + '\'' + ", size=" + size + ", price=" + price + '}'; } }
通过Serializable实现序列化非常简单,实现完Serializable接口的Gift就是一个可以进行序列化与反序列化的对象。那么如何进行序列化和反序列化呢?也非常简单:
try {
//序列化过程
Gift giftSrc = new Gift("lv", 20, 10000f);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(Environment.getExternalStorageDirectory().toString() + "/gift.txt"));
out.writeObject(giftSrc);
out.close();
//反序列化过程
ObjectInputStream in = new ObjectInputStream(new FileInputStream(Environment.getExternalStorageDirectory().toString() + "/gift.txt"));
Gift giftSerial = (Gift) in.readObject();
in.close();
Log.e(TAG, "反序列化:" + giftSerial.toString());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
运行结果:
可以看到,和我们之前序列化的Gift对象内容一致。
上述序列化的过程:把实现了Serializable接口的Gift通过流写入到本地文件。
反序列的过程:通过流把本地文件中的内容读取出来,构建一个Gift对象,不过这时候的Gift对象与原来的Gift虽然内容一致,但是其本质并不是同一个对象。
serialVersionUID的作用:
serialVersionUID被称为序列化版本号,鉴别反序列时生成的类对象是不是之前序列化的类对象。
我们之前说过不手动生成serialVersonUID也是可以实现序列化的,只是对反序列化有些影响。那么我们就来详细的说一下手动和自动两种情况。
1.隐式:不手动生成,系统会根据类的结构自动生成其Hash值。而我们在序列化的时候会把生成serialVersionUID也一起写进本地文件。反序列化时系统会把之前写进去的serialVersionUID和之前写进去的做对比,如果一致,那么可以反序列化,如果不一致,那么反序列化失败Cash! 所以系统自动生成这种方式对对象的一致性要求很高,不能随意增删改。例如删除一个成员变量,那么系统会自动计算当前类的hash值并把它赋值给serialVersionUID,那么当前类的serialVersionUID就和反序列话中的不一致,造成反序列化失败。
2.显式:比如1L或者根据类名,接口名,成员方法以及属性等生成一个64为的hash并赋值给serialVersionUID,其余的参数我们可以进行修改,这种方式可以很大程度上避免反序列化的失败。
代码示例:在上面的基础上我们使用自动生成,序列化之后我们更改一个成员变量。
public static class Gift implements Serializable {
// public static final long serialVersionUID = 8098080809810941L;
public String name;
public int size;
public float price;
public String place;
public Gift(String name, int size, float price, String place) {
this.name = name;
this.size = size;
this.price = price;
this.place = place;
}
@Override
public String toString() {
return "Gift{" +
"name='" + name + '\'' +
", size=" + size +
", price=" + price +
", place='" + place + '\'' +
'}';
}
}
反序列化运行结果:
很明显由于我们修改了Gift的成员变量,导致系统自动重新计算生成了一个新的serialVersionUID,其与反序列化中我们之前写进去的不同,所以不认为是同一个类,反序列化失败。
Tips:静态成员不属于类所以不参与序列化,transient关键字同样。
注1:标识接口是没有任何方法和属性的接口。标识接口不对实现它的类有任何语义上的要求,它仅仅表明实现它的类属于一个特定的类型。
标接口在Java语言中有一些很著名的应用,比如java.io.Serializable和java.rmi.Remote等接口便是标识接口。标识接口,当一个类实现了一个标识接口之后就像是给自己打了个标签。
打个比喻,不是很恰当。就像是一个人穿了件名牌衣服(实现了标识接口),别人一看他穿的衣服(标识接口)就知道他的品味、身份(特性)。