Java序列化Serializable & Externalizable
当需要将对象本地化存储而非仅内存,或者将对象以及相应信息转换为字节序列写入I/O流进行网络传输,然后通过反序列化提取对象及其相关信息而避免重复设置值。Java提供接口为:Serializable 和 Externalizable。
Serializable:
1.基本用法:(基于JDK1.8)
//需要序列化的实体类只需实现接口即可
public class Person implements Serializable {
private final static long serialVersionUID = 1L;
private String name;//姓名
private String psw;//密码
public Person(){}
public Person(String n,String p){
this.name = n;
this.psw = p;
}
public String getName(){
return name;
}
public String getPsw(){
return psw;
}
}
//测试代码
//序列化对象,执行之后在Idea的src文件夹下出现person.obj文件
private static void writeObjByS(){//static非必须
Person p = new Person("chen","1234");
try {
ObjectOutputStream objOutStream = new ObjectOutputStream(
new FileOutputStream("src/person.obj"));
objOutStream.writeObject(p);
objOutStream.flush();
objOutStream.close();//务必执行
} catch (IOException e) {
e.printStackTrace();
}
}
//反序列化对象
private static void readObjByS(){
Person p;
try {
ObjectInputStream objStream = new ObjectInputStream(
new FileInputStream("src/person.obj"));
p = (Person)objStream.readObject();
objStream.close();//务必执行
System.out.println("log the message is --> name:"
+ p.getName() + " psw = " +p.getPsw());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
//打印结果:log the message is --> name:chen psw = 1234
2.更多细节
(1) serialVersionUID :
首先是声明问题,在接口中是这样定义的:
If a serializable class does not explicitly declare a serialVersionUID,
then the serialization runtime will calculate a default serialVersionUID value
for that class based on various aspects of the class ---Java
也就是说你不显示声明的时候系统会默认创建,那到底有啥作用哦?
举个例子:在上文的序列化写入文件之后,反序列化之前,修改本地的serialVersionUID的值(与序列化时不一致),果然抛出异常,反序列化失败:
所以说作用是在反序列化的时候,将字节序列中的serialVersionUID与本地中的实体类中的serialVersionUID对比,不一致的话就会抛出:
java.io.InvalidClassException;local class incompatible 等异常信息!
但是还是推荐设置UID值,方便升级。
(2)transient:使用transient修饰的成员变量不会被序列化(避免了敏感信息在网络传输中泄漏的风险,比如password)。读者可自行验证呀!
(3)static:static修饰的变量不会被序列化,例子如下:
//在Person添加如下字段 (打印字段也添加了)
private static int age;//注意这个地方可没有赋值
public void setAge(int newAge){
age = newAge;
System.out.println("change age to :" + newAge);
}
public int getAge(){ return age;}
//序列化之前,设置static age = 66;
Person p = new Person("chen","1234");
p.setAge(66);
最终,打印age值为0,但我在序列化之前设置age值为66了呀?
再试着将age初始值设置为22,其余一切如旧,结果打印的是初始值22而非66。可见对于所有实例对象共享的静态变量,有一说一,确实不能被序列化,读取的也只是默认值(或者初始值)而已。
Externalizable:
1.基本用法
public class DogE implements Externalizable {
private final static long serialVersionUID = 1L;
private String address;
private int age;
private int sons;
public DogE(){}//这里无参构造函数是必须的
public DogE(String add,int age,int sons){
this.address = add;
this.age = age;
this.sons = sons;
}
public String getAddress() { return address; }
public int getAge() { return age; }
public int getSons() { return sons; }
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(address);
out.writeInt(age);
out.writeInt(sons);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.address = in.readUTF();
this.age = in.readInt();
this.sons = in.readInt();
}
}
//测试代码 序列化
private static void writeByEZ(){
DogE dogE = new DogE("m",22,2);
ObjectOutputStream objOStream;
try {
objOStream = new ObjectOutputStream(new FileOutputStream("src/doge.txt"));
dogE.writeExternal(objOStream);
objOStream.flush();
objOStream.close();//这里务必执行,不然read时候可能会有EOF异常
} catch (IOException e) {
e.printStackTrace();
}
}
//反序列化
private static void readByEZ(){
DogE d = new DogE();
ObjectInputStream objInStream;
try {
objInStream = new ObjectInputStream(new FileInputStream("src/doge.txt"));
d.readExternal(objInStream);
objInStream.close();
System.out.println("log msg: address --> " +
d.getAddress() + " age:" +d.getAge() + " sons:" + d.getSons());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
FBI WARNING:实体类中变量的写入顺序和读出顺序应该一致!
//顺序错但是类型没有出错时也会顺利读出,但是age值赋给了sons,注意别出差哦
//写
out.writeInt(age);
out.writeInt(sons);
//读
this.sons = in.readInt();
this.age = in.readInt();
总结:
相较于序列化方式,Serializable对整个类和信息进行序列化,而Externalizable则可以选择需要的序列化对象。
附带:
Android中使用Intent进行activity之间传递信息,使用的序列化为:Parcelable
实例:
public class LoginInfo implements Parcelable {
private String name;
private int age;
private String psw;
private LoginInfo(Parcel in) {
this.name = in.readString();
this.age = in.readInt();
this.psw = in.readString();
}
public LoginInfo(String name,int age,String psw){
this.name = name;
this.age = age;
this.psw = psw;
}
@NonNull
@Override
public String toString() {
return "name --> " + name +" ;age --> " + age +" ;psw --> "+ psw;
}
@Override
public int describeContents() {//返回默认值即可
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
dest.writeInt(age);
dest.writeString(psw);
}
//底层调用
public static final Creator<LoginInfo> CREATOR = new Creator<LoginInfo>() {
@Override
public LoginInfo createFromParcel(Parcel in) {
return new LoginInfo(in);
}
@Override
public LoginInfo[] newArray(int size) {
return new LoginInfo[size];
}
};
}
Intent使用:
//启动
LoginInfo info = new LoginInfo("cheng",22,"1234");
Bundle bundle = new Bundle();
bundle.putParcelable("login_info",info);
Intent intent = new Intent(this,IntentReceiverActivity.class);
intent.putExtras(bundle); //可简写为:intent.putExtra("login_info",info)
startActivity(intent);
//接收
Intent intent = getIntent();
LoginInfo info = intent.getParcelableExtra("login_info");
注:当向另一应用使用Intent发送数据时,请勿使用Parcelable或者Serializable,对方无完全相同的无序列化类,则会抛出RuntimeException