Serializable关键字平时我们应该很少有机会去使用到,平时的业务中不需要我们去做对象状态的保存,而且一般都是中间件或者框架内才使用到。但是关于Serializable应该要去理解和学习。
1. 怎么理解序列化
1.首先,在我们平时运行项目时,JVM处于运行中,此时工程内的对象是存活状态,因此这些对象的生命周期是会比JVM的生命周期短的。然后有的情况下,我们需要持久化一些特殊对象,以便将来能够读取被保存的对象。Serializable就是Java给出的解决方案。
2. 当我们去进行一些简单试验时发现,序列化时,会将状态保存为一组字节,在反序列化时又将字节组装成对象。因此整个过程是对字节的操作,不能使用字符操作API。同时,序列化时是保存的对象的瞬时状态,也就是成员变量的值,因此,静态变量就不会被保存下来。
2. 示例
2.1 Person类。
public class Person implements Serializable {
private String name;
private int age;
private String phone;
public Person(String name, int age, String phone) {
this.name = name;
this.age = age;
this.phone = phone;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "[ name = " + name
+" age = " + age
+" phone = " + phone + " ]";
}
}
2.2 序列化操作。写到本地txt文件中
public class OutputTest {
public static void main(String[] args) throws Exception{
File file = new File("D:" + File.separator + "person.txt");
ObjectOutputStream oos = null;
OutputStream outputStream = new FileOutputStream(file);
oos = new ObjectOutputStream(outputStream);
oos.writeObject(new Person("小刚", 23,"152444444444"));
outputStream.close();
oos.close();
}
}
2.3 反序列化读取。
public class InputTest {
public static void main(String[] args) throws Exception{
File file = new File("D:" + File.separator + "person.txt");
ObjectInputStream ois = null;
InputStream inputStream = new FileInputStream(file);
ois = new ObjectInputStream(inputStream);
Object obj = ois.readObject();
Person person = (Person) obj;
ois.close();
inputStream.close();
System.out.println(person.toString());
}
}
[ name = 小刚 age = 23 phone = 152444444444 ]
3. 从示例入手,思考和验证几个问题(都是个人观点和总结)
3.1 为什么在序列化时都需要实现Serializable接口?
Serializable接口里没有任何代码,因此可以说Serializable接口只是一个标识作用,代表了你想序列化就要实现这个接口,只有实现这个接口之后才知道这个对象能被序列化。通过源码我们能发现什么呢?是可以发现一点东西的。
在序列化操作ObjectOutputStream里的writeObject中有如下代码:
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
我们可以发现几个事实:
1. 序列化操作会根据对象的实际类型不同执行不同的序列化操作。
2. String,数组,枚举可以直接进行序列化。
3. 那么自定义对象如何才能被识别可以进行序列化呢?就是靠是否实现了Serializable接口识别的,所以它是一个标识功能。
3.2 readObject时这个对象是怎么出来的?构造函数被执行了么?
我们在Person类里的够造函数加一句输出,来看看。
1. 当序列化时,输出如下语句:
构造函数被执行了
2. 反序列化时,输出如下语句:
[ name = 小刚 age = 23 phone = 152444444444 ]
可以看见在反序列化时构造函数没有被执行,反序列化时读取的是保存对象的完整字节数据,这个数据就是一个完整的对象,不需要再进行构造函数构造出来了。
3.3 序列化和反序列化之后的对象之间是什么关系,== 或者 equals?
基于此我们来做下试验。代码如下:
Person p1 = new Person("小刚", 23,"152444444444");
//序列化到person1.txt
File file1 = new File("D:" + File.separator + "person1.txt");
ObjectOutputStream oos = null;
OutputStream outputStream = new FileOutputStream(file1);
oos = new ObjectOutputStream(outputStream);
//写两次
oos.writeObject(p1);
oos.writeObject(p1);
outputStream.close();
oos.close();
//序列化到person2.txt
File file2 = new File("D:" + File.separator + "person2.txt");
ObjectOutputStream oos2 = null;
OutputStream outputStream2 = new FileOutputStream(file2);
oos2 = new ObjectOutputStream(outputStream2);
oos2.writeObject(p1);
outputStream.close();
oos2.close();
//读取person1.txt中的内容
ObjectInputStream ois1 = null;
InputStream is1 = new FileInputStream(file1);
ois1 = new ObjectInputStream(is1);
Person p2 = (Person)ois1.readObject();
Person p3 = (Person)ois1.readObject();
ois1.close();
is1.close();
//读取person2.txt中的内容
ObjectInputStream ois2 = null;
InputStream is2 = new FileInputStream(file2);
ois2 = new ObjectInputStream(is2);
Person p4 = (Person) ois2.readObject();
ois2.close();
is2.close();
//读取person1.txt中的内容
ObjectInputStream ois3 = null;
InputStream is3 = new FileInputStream(file1);
ois3 = new ObjectInputStream(is3);
Person p5 = (Person)ois3.readObject();
ois3.close();
is3.close();
System.out.println("序列化前的对象: "+ p1);
System.out.println("序列化Person1.txt对象p2: "+ p2);
System.out.println("序列化Person1.txt对象p3: "+ p3);
System.out.println("序列化Person2.txt对象p4: "+ p4);
System.out.println("序列化Person1.txt对象p5: "+ p5);
System.out.println("前后比较: " + (p1 == p2));
System.out.println("前后比较: " + (p1 == p3));
System.out.println("前后比较: " + (p1 == p4));
System.out.println("前后比较: " + (p1 == p5));
System.out.println("读取相同文件-----同一个流-----: " + (p2 == p3));
System.out.println("读取相同文件-----不同流读-----: " + (p2 == p5));
System.out.println("读取不同文件-----不同流读-----: " + (p2 == p4));
序列化前的对象: [ name = 小刚 age = 23 phone = 152444444444 ] Person@6d6f6e28
序列化Person1.txt对象p2: [ name = 小刚 age = 23 phone = 152444444444 ] Person@58372a00
序列化Person1.txt对象p3: [ name = 小刚 age = 23 phone = 152444444444 ] Person@58372a00
序列化Person2.txt对象p4: [ name = 小刚 age = 23 phone = 152444444444 ] Person@4dd8dc3
序列化Person1.txt对象p5: [ name = 小刚 age = 23 phone = 152444444444 ] Person@6d03e736
前后比较: false
前后比较: false
前后比较: false
前后比较: false
读取相同文件-----同一个流-----: true
读取相同文件-----不同流读-----: false
读取不同文件-----不同流读-----: false
可以总结几个结论:
1. 序列化前后的对象地址不同了。内容保持一致。
2. 同一个流中序列化时的对象再读取出来时是同一个对象。
3.4 引用类型变量是深拷贝还是浅拷贝?
添加一个引用类型变量Data
public class Data implements Serializable {
private String num;
public Data(String num) {
this.num = num;
}
@Override
public String toString() {
return "num: " + num + "[ " + super.toString()+" ]";
}
}
序列化前的对象: [ name = 小刚 age = 23 phone = 152444444444 Data = num: 第一个[ Data@45ee12a7 ] ] Person@135fbaa4
序列化Person1.txt对象p2: [ name = 小刚 age = 23 phone = 152444444444 Data = num: 第一个[ Data@2d98a335 ] ] Person@16b98e56
序列化Person1.txt对象p3: [ name = 小刚 age = 23 phone = 152444444444 Data = num: 第一个[ Data@2d98a335 ] ] Person@16b98e56
序列化Person2.txt对象p4: [ name = 小刚 age = 23 phone = 152444444444 Data = num: 第一个[ Data@7ef20235 ] ] Person@27d6c5e0
序列化Person1.txt对象p5: [ name = 小刚 age = 23 phone = 152444444444 Data = num: 第一个[ Data@4f3f5b24 ] ] Person@15aeb7ab
前后比较: false
前后比较: false
前后比较: false
前后比较: false
读取相同文件-----同一个流-----: true
读取相同文件-----不同流读-----: false
读取不同文件-----不同流读-----: false
从输出可以看出:是深拷贝,每一个的地址都不同,但是值是一样的。也就是说序列化是序列化的对象整个的关系网(个人理解)。
3.5 UID的理解?
3.5.1 测试方法级别的变动
Person类在序列化之后添加了一个普通的方法,代码如下:
public void testMethod(){
}
Exception in thread "main" java.io.InvalidClassException: Person; local class incompatible: stream classdesc serialVersionUID = -1060766909201190420, local class serialVersionUID = -1321389278186723799
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
at InputTest.main(InputTest.java:23)
可以看见此时会出现反序列化失败的场景,因为UID改变了。
3.5.2 测试属性级别的变动
private String data1;
private int data2;
Exception in thread "main" java.io.InvalidClassException: Person; local class incompatible: stream classdesc serialVersionUID = -1060766909201190420, local class serialVersionUID = 5010219192652467572
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
at InputTest.main(InputTest.java:23)
一样的场景。
3.5.3 添加一个默认的UID
private static final long serialVersionUID = 1813544648148214900L;
private String name;
private int age;
private String phone;
private Data data;
private String address;
private int num;
在反序列化手动添加address和num两个成员变量。
[ name = 小刚 age = 23 phone = 152444444444 Data = num: 第二个[ Data@7cca494b ] address = null num = 0 ] Person@7ba4f24f
可以看出针对本地新增的改变的成员变量的值会有一个默认值给出。
3.5 序列化中的重构?
当序列化之后,我们只要新增一个UID就可以对此类进行重构了,3.4 内可以看出可以新增成员变量,会给出默认值。那么如果是针对静态变量的改变呢?我们知道静态变量属于类,不属于对象,所以静态变量是不会被序列化到文件中去的。那么我们将静态变量改变为非静态变量,其实也就相当于进行了3.4的操作,是可以的。
那么我们可以将之前的非静态的成员变量改变为静态变量吗?代码如下:
[ name = null age = 23 phone = 152444444444 Data = num: 第二个[ Data@7cca494b ] num = 0 ] Person@7ba4f24f
发现是可以的,依然是存在一个默认值。
3.5 transient?
transient字段标注了这个字段是不需要序列化到文件中的,具体不去操作了。