引入:序列化的三个小问题
- 啥叫序列化和反序列化?
- 为什么要序列化呢?
- 为什么类中有serialVersionUID这样一个值,他体现的作用在哪里
无数次被序列化所困惑,于是今天好好进行整理,写在前面,这个Serializable接口,以及相关的东西,全部都在 Java io 里面的,这点是要额外注意下的。
1-1 定义
- 序列化:把对象转换为字节序列的过程称为对象的序列化。
- 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
通俗来说,序列化就是将一个对象中的有用的信息,进行持久化,持久化就是说存到硬盘里面,而不是在内存中。所以,序列化的目的还是在于将对象的状态放在一个地方,不管是文件也好,还是网络传递也好。
1-2 几种常见的需要序列化的场景
- 把内存中的对象的状态持久化到一个对象或者是数据库中
- 在网络上传输对象的时候
- RMI(Remote Method Invocation)远程方法调用的时候
我自己只玩过第一个,第三个远程方法调用,我觉得会在我现在学的分布式微服务中得以体现,值得期待。
1-3 Demo(持久化到文件和库中)
- Person.java
package 序列化;
import java.io.Serializable;
public class Person implements Serializable {
private static final long serialVersionUID = -5090861933306306871L;
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- WriteObj2File.java
package 序列化;
import com.alibaba.fastjson.JSON;
import java.io.*;
public class WriteObj2File {
public static void writeObj(String fileName) {
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName));
Person person = new Person("xx");
oos.writeObject(person);
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void readObj(String fileName) {
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
Person p = (Person) ois.readObject();
System.out.println(p.getName());
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* JSON序列化存库测试
*/
public static void jsonTest() {
Person person = new Person("xx");
String jsonString = JSON.toJSONString(person);
System.out.println(jsonString);
// 存入库中 TODO
// 读库 TODO
Person person1 = JSON.parseObject(jsonString, Person.class);
String person1Name = person1.getName();
System.out.println(person1Name);
}
public static void main(String[] args) {
// String fileName = "${填写你自己的路径}";
// writeObj(fileName);
// readObj(fileName);
jsonTest();
}
}
- 小解释: 这里的代码都比较简单,搜索了资料,正常来说,存db里json字符串比较合适,我也没有实际用过,所以只是作为参考。存文件的是经典demo,没有问题的。也很好测试。
2-1 序列化经典小问题
2-1-1 不是类中所有的属性都可以被持久化的
声明为 static 和 transient 类型的成员变量不能被序列化。因为 static 代表类的状态,transient 代表对象的临时数据。
首先是transient
。
Person2.java
package 序列化;
import java.io.Serializable;
public class Person2 implements Serializable {
private static final long serialVersionUID = 2952953958174783379L;
private String name;
private transient int age;
private static String classData = "a";
public Person2(String name, int age) {
this.name = name;
this.age = age;
}
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 static String getClassData() {
return classData;
}
public static void setClassData(String classData) {
Person2.classData = classData;
}
@Override
public String toString() {
return "Person2{" +
"name='" + name + '\'' +
", classData='" + classData + '\'' +
", age=" + age +
'}';
}
}
- Test.java
package 序列化;
import java.io.*;
public class Test {
public static void writeObj(String fileName) {
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName));
Person2 person = new Person2("xx", 12);
oos.writeObject(person);
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void readObj(String fileName) {
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName));
Person2 p = (Person2) ois.readObject();
System.out.println(p);
ois.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
String fileName = "/Users/xiaoxin/各类项目/java/java_project/basicLearning/src/main/java/序列化/person2.txt";
writeObj(fileName);
readObj(fileName);
}
}
----output----
Person2{name='xx', classData='a', age=0}
可以看出,这里的transient
age,并不是我们想象中的12,而是0,所以序列化是失败的,也就是说,带transient
这个关键字的属性是无法序列化的。
下面来测试静态变量static,先把静态变量的值改成b,同时,只执行反序列化操作,看是否会将静态属性变回a。
private static String classData = "b";
public static void main(String[] args) {
String fileName = "/Users/xiaoxin/各类项目/java/java_project/basicLearning/src/main/java/序列化/person2.txt";
readObj(fileName);
}
----output----
Person2{name='xx', classData='b', age=0}
分析:可以比较明显看出,我们文件里面classData是a,这边确显示的是b,也就是说,这个属性并没有序列化成功。所以静态变量也不会序列化成功的。
2-1-2 为什么类中有serialVersionUID这样一个值,他体现的作用在哪里?
序列化接口是一定要的,这个没啥好质疑的,因为有的方法里面的参数对象,就必须实现这个接口。
我们先把序列化ID注释掉,来进行一次序列化和反序列化操作。
package 序列化;
import java.io.Serializable;
public class Person2 implements Serializable {
// private static final long serialVersionUID = 2952953958174783379L;
private String name;
private transient int age;
public Person2(String name, int age) {
this.name = name;
this.age = age;
}
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;
}
@Override
public String toString() {
return "Person2{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) {
String fileName = "/Users/xiaoxin/各类项目/java/java_project/basicLearning/src/main/java/序列化/person2.txt";
writeObj(fileName);
readObj(fileName);
}
无事发生,一切正常。那么如果这个时候,我自己突然加了一个属性呢?
package 序列化;
import java.io.Serializable;
public class Person2 implements Serializable {
// private static final long serialVersionUID = 2952953958174783379L;
private String name;
private transient int age;
private double height;
public Person2(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
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 double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public String toString() {
return "Person2{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
}
public static void main(String[] args) {
String fileName = "/Users/xiaoxin/各类项目/java/java_project/basicLearning/src/main/java/序列化/person2.txt";
// writeObj(fileName);
readObj(fileName);
}
----output----
java.io.InvalidClassException: 序列化.Person2; local class incompatible: stream classdesc serialVersionUID = 7030615533941445544, local class serialVersionUID = -7417946223875850957
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2003)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1850)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2160)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1667)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:503)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:461)
at 序列化.Test.readObj(Test.java:20)
at 序列化.Test.main(Test.java:31)
分析:这边直接显示序列化ID不对了,为什么呢?我们类里面是没有手动定义这个serialVersionUID
的那么,系统会给我们根据属性自动生成一个,我们第一次序列化的时候,进入本地是一个serialVersionUID,后面我们添加了一个属性,系统给我们生成了另一个serialVersionUID,很明显,这两个serialVersionUID,一定是不相同的。现在我们再手动加回自己的serialVersionUID,看效果。就会发现一切都正常了。所以,由此可见,serialVersionUID的作用就很明显了。这个id一旦定下来,就绝对不能改,也必须要自己添加好,不然碰到序列化和反序列化的时候,这个问题一定会出来。
2-1-3 如何设置serialVersionUID?
Idea 可以帮你搞定,具体方法看下面这个链接。https://blog.csdn.net/wd2014610/article/details/100735074,别人的,就当是帮他引流了哈哈哈哈。
2-1-4 如果成员变量也是一个类呢?
注意,成员变量是一个类的话,这个类也要实现序列化接口的,不然会报错java.io.NotSerializableException:
2-1-5 如果还有相关问题可以再进行补充,上面的是比较常见的
确实有参考,我做了点归纳,原作:https://blog.csdn.net/qq_27093465/article/details/78544505?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163289923216780271562162%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=163289923216780271562162&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2alltop_positive~default-1-78544505.pc_search_result_control_group&utm_term=%E5%BA%8F%E5%88%97%E5%8C%96&spm=1018.2226.3001.4187