本文主要解读java中的对象序列化部分,以及指定某些参数属性进行序列化和子对象的重新重建;也是记录学习“java编程思想”书籍的历程。
对象的序列化
我们在进行对象的序列化时,能够追踪到对象的所有引用,形象来说就是“对象网”。java序列化时会用优化的算法自动维护整个对象网。废话不多说,来上代码:
class Data implements Serializable{
private int n;
public Data(int n) {
this.n = n;
}
@Override
public String toString() {
return "Data{" +
"n=" + n +
'}';
}
}
public class Alien implements Serializable {
private static Random random = new Random(18);
private Data[] datas = {
new Data(1),
new Data(2),
new Data(2)
};
private Alien next;
private char c;
public Alien(int i, char x) {
System.out.println("Alien 构造器:" + i);
c = x;
if (--i > 0) {
next = new Alien(i, (char) (x + 1));
}
}
public Alien() {
System.out.println("默认构造器...");
}
@Override
public String toString() {
return "Alien{" +
"datas=" + Arrays.toString(datas) +
", next=" + next +
", c=" + c +
'}';
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
String filePath = "alien.out";
Alien alien = new Alien(5, 'b');
System.out.println(alien);
System.out.println("序列化保存对象");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
oos.writeObject(alien);
oos.close();
System.out.println("反序列化读取对象");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
Alien alien1 = (Alien) ois.readObject();
System.out.println(alien.hashCode());
System.out.println(alien1.hashCode());
System.out.println(alien1 == alien);
}
}
Alien 构造器:5
Alien 构造器:4
Alien 构造器:3
Alien 构造器:2
Alien 构造器:1
Alien{datas=[Data{n=1}, Data{n=2}, Data{n=2}], next=Alien{datas=[Data{n=1}, Data{n=2}, Data{n=2}], next=Alien{datas=[Data{n=1}, Data{n=2}, Data{n=2}], next=Alien{datas=[Data{n=1}, Data{n=2}, Data{n=2}], next=Alien{datas=[Data{n=1}, Data{n=2}, Data{n=2}], next=null, c=f}, c=e}, c=d}, c=c}, c=b}
序列化保存对象
反序列化读取对象
325040804
664223387
false
注意点:
1.序列化的对象必须实现Serializable接口
2.序列化和反序列化之后的对象属性值都是相同的,但不是一个对象,hashCode不一致。
3.序列化对象会将所有的子对象一起序列化;
定制序列化
从上面的实例,我们看到java对象的序列化其实并不难操作。只要实现指定的接口即可。但是有时我们想序列对象的部分属性,或者一些敏感属性不想序列化存储,这时我们应该如何操作呢?
这里我们用两种方法实现:
1.实现Externalizable接口,重写readExternal和writeExternal方法;
public class Blip3 implements Externalizable {
private int i;
private String s;
//无参构造方法
public Blip3(){
System.out.println("无参构造方法被调用...");
}
public Blip3(String x,int a){
System.out.println("有参构造方法被调用======");
this.s = x;
this.i = a;
}
@Override
public String toString() {
return "Blip3{" +
"i=" + i +
", s='" + s + '\'' +
'}';
}
/**
* 选择性的将对象和属性进行写出到文件
* @param out
* @throws IOException
*/
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("blip3 writeExternal...");
out.writeObject(s);
// out.writeInt(i);
}
/**
* 先调用readExternal,在进行有参构造
* @param in
* @throws IOException
* @throws ClassNotFoundException
*/
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
//属性从文件中读出
System.out.println("blip3 readExternal");
s = (String) in.readObject();
// i = in.readInt();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
System.out.println("构造对象");
Blip3 b3 = new Blip3("A String", 47);
System.out.println(b3);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("blip3.out"));
System.out.println("保存对象b3");
oos.writeObject(b3);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("blip3.out"));
System.out.println("从文件中恢复对象");
Blip3 readB3 = (Blip3) ois.readObject();
System.out.println(readB3);
System.out.println(b3==readB3);
System.out.println(b3.hashCode());
System.out.println(readB3.hashCode());
}
}
构造对象
有参构造方法被调用======
Blip3{i=47, s=‘A String’}
保存对象b3
blip3 writeExternal…
从文件中恢复对象
无参构造方法被调用…
blip3 readExternal
Blip3{i=0, s=‘A String’}
false
356573597
81628611
在反序列化对象时,会先调用无参构造方法,然后调用readExternal方法将属性值赋值。
2.使用transient(瞬时)关键字
在使用transient关键字逐个字段的关闭序列化,它的意思是
“不用麻烦你保存或者回复数据——我自己会处理的”
public class Login implements Serializable {
private Date date = new Date();
private String userName;
private transient String password;
public Login(String userName,String password){
this.userName = userName;
this.password = password;
}
@Override
public String toString() {
return "Login{" +
"date=" + date +
", userName='" + userName + '\'' +
", password='" + password + '\'' +
'}';
}
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
String filePath = "login.out";
//序列化对象
Login login = new Login("bob", "abcde");
System.out.println("saving object");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
oos.writeObject(login);
oos.close();
System.out.println(login);
Thread.sleep(1);
//反序列化读取对象
System.out.println("recovering object");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath));
Login login1 = (Login)ois.readObject();
System.out.println(login1);
}
}
saving object
Login{date=Sat Sep 12 13:37:41 CST 2020, userName=‘bob’, password=‘abcde’}
recovering object
Login{date=Sat Sep 12 13:37:41 CST 2020, userName=‘bob’, password=‘null’}
这里我们发现带有transient关键字的password属性,并没有存储到磁盘中被加载出来。
由于Externalizable对象在默认情况下不保存它们的任何属性,所以transient关键字和Serializable对象一起使用。
我们在进行序列化和反序列化时都需要对应的Class对象,如果java虚拟机找不到对相应的Class文件,就会得到一个ClassNotFoundException异常