概念
序列化:将Java对象转换为字节序列的过程
反序列化:将字节序列转换为Java对象的过程
为什么要这么操作
我们都知道可以通过网络传递图片、视频、文件等等数据信息,而这些信息最终都是以二进制序列传递的。那么Java对象呢?如果两个系统间要通过Java对象进行通信改怎么办,这个时候就要使用到Java的序列化和反序列化了。
而序列化的好处是什么呢?首先序列化可以永久存储对象信息,我们可以将对象序列化后存储到磁盘上,这样可以保存对象数据;其次可以通过这个方式完成跨网络的通信和信息传递
实现
序列化相关的API
对象输出流
对象输出类 public ObjectOutputStream(OutputStream out) throws IOException 写入对象方法 public final void writeObject(ObjectOutput out) throws IOException
对象输入流
对象输入类 public ObjectInputStream(InputStream in) throws IOException 读取对象方法 public final void readObject(ObjectInput in) throws IOException
实现序列化的要求
必须实现接口Serializable 或者 Externalizable 才能被序列化,否则会报错
实现方式
假设以对象student为例,假设对该对象进行序列化和反序列化
若只实现了Serializable接口则
* ObjectOutputStream采用默认序列化方式,对非transient修饰变量进行序列化 * ObjectInputStream 采用默认的反序列化方式,对非transient修饰的变量进行反序列化
如果实现了Serializable接口,并且也定义了writeObject(ObjectOutput out) 和 readObject()方法
* ObjectOutputStream会调用类中的writeObject方法进行序列化 * ObjectInputStream同样会调用类中的readObject进行反序列化
如果实现了Externalizable接口,必须实现writeExternal(ObjectOutput out) 和 readExternal(ObjectInput in)方法
* 类必须有一个无参构造器方法(创建类是默认会有一个无参构造方法,但最好还是手动写一个) * ObjectOutputStream调用Student对象的writeExternal(ObjectOutput out))的方法进行序列化。 * ObjectInputStream会调用Student对象的readExternal(ObjectInput in)的方法进行反序列化。 如果实现了Externalizable接口而writeExternal(ObjectOutput out) 和 readExternal(ObjectInput in)方法没有进行任何处理,默认不对对象做任何序列化操作。
步骤
创建一个对象输出流
ObjectOutputStream out = new ObjectOutputStream(new FileInputStream("D:\\test.txt"));
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
- 将要序列化的对象写入对象输出流
out.writeObject(“Hello”);
out.writeObject(new Date());
- 创建一个对象输入流
ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\\test.txt"));
- 读取对象
String obj1 = (String)in.readObject();
Date obj2 = (Date)in.readObject();
案例
采用Serializable方法实现
以stuendt为例,注意变量address被transient修饰,关于transient的用法下面会详细说明,请先往下看
public class Student implements Serializable {
private String name;
private int age;
private String sex;
private transient String address;
/**
* Getter method for property name.
*
* @return property value of name
*/
public String getName() {
return name;
}
/**
* Setter method for property name
*
* @param name value to be assigned to property name
*/
public void setName(String name) {
this.name = name;
}
/**
* Getter method for property age.
*
* @return property value of age
*/
public int getAge() {
return age;
}
/**
* Setter method for property age
*
* @param age value to be assigned to property age
*/
public void setAge(int age) {
this.age = age;
}
/**
* Getter method for property sex.
*
* @return property value of sex
*/
public String getSex() {
return sex;
}
/**
* Setter method for property sex
*
* @param sex value to be assigned to property sex
*/
public void setSex(String sex) {
this.sex = sex;
}
/**
* Getter method for property address.
*
* @return property value of address
*/
public String getAddress() {
return address;
}
/**
* Setter method for property address
*
* @param address value to be assigned to property address
*/
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
运行结果如下:
public static void main(String[] args) throws Exception {
Student stu = new Student();
stu.setName("A");
stu.setAge(10);
stu.setSex("man");
stu.setAddress("CHN");
//1、序列化:创建对象输出流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
//2、写入序列化对象
out.writeObject(stu);
//3、创建对象输入流
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bis);
//4、对取对象
Student obj = (Student)in.readObject();
System.out.println(obj);
}
运行结果
Student{name='A', age=10, sex='man', address='null'}
Process finished with exit code 0
可以看到对象对顺利序列化了,但是被transient修饰的变量没有被序列化,这是为什么呢?
transient的用法
在日常开发中经常会遇到这种情况,有些类中存在敏感信息,这些信息想让使用该类对象的人知道,比如用户的身份证信息等,这个时候就可以在不想让对方知道的变量前加上transient关键字,被transient修饰的变量不会被序列化和反序列化。当我们在实际开发中发现某个对象的变量不能被序列化时,就要考虑是不是变量加了transient关键字。
使用场景
- transient只能修饰变量,不能修饰类和方法
- 被transient修饰的变量不能持久化,因为该变量无法序列化也就无法存储到磁盘中
- 对于静态变量,无论有没有被transient修饰,都无法序列化
前两点已经在上面的例子中有所展示,下面我们看下第三点
首先我们增加一个静态变量school
public class Student implements Serializable {
private String name;
private int age;
private String sex;
private transient String address;
public static String school = "QHDX";
/**
* Getter method for property name.
*
* @return property value of name
*/
public String getName() {
return name;
}
/**
* Setter method for property name
*
* @param name value to be assigned to property name
*/
public void setName(String name) {
this.name = name;
}
/**
* Getter method for property age.
*
* @return property value of age
*/
public int getAge() {
return age;
}
/**
* Setter method for property age
*
* @param age value to be assigned to property age
*/
public void setAge(int age) {
this.age = age;
}
/**
* Getter method for property sex.
*
* @return property value of sex
*/
public String getSex() {
return sex;
}
/**
* Setter method for property sex
*
* @param sex value to be assigned to property sex
*/
public void setSex(String sex) {
this.sex = sex;
}
/**
* Getter method for property address.
*
* @return property value of address
*/
public String getAddress() {
return address;
}
/**
* Setter method for property address
*
* @param address value to be assigned to property address
*/
public void setAddress(String address) {
this.address = address;
}
/**
* Getter method for property school.
*
* @return property value of school
*/
public String getSchool() {
return school;
}
/**
* Setter method for property school
*
* @param school value to be assigned to property school
*/
public void setSchool(String school) {
Student.school = school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
", school='" + school + '\'' +
'}';
}
}
运行结果如下:
public static void main(String[] args) throws Exception {
Student stu = new Student();
stu.setName("A");
stu.setAge(10);
stu.setSex("man");
stu.setAddress("CHN");
//1、序列化:创建对象输出流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
//2、写入序列化对象
out.writeObject(stu);
//3、创建对象输入流
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bis);
//4、对取对象
Student obj = (Student)in.readObject();
System.out.println(obj);
}
运行结果:
Student{name='A', age=10, sex='man', address='null', school='QHDX'}
Process finished with exit code 0
有人会说school被打印出来,显然是被序列化后。
其实这个地方school并没有被序列化,因为school是静态变量,所以这个地方是从静态数据存储区直接获取的school值,不信我们看下面这段代码,在对象序列化后对school进行修改,我们看下结果
public static void main(String[] args) throws Exception {
Student stu = new Student();
stu.setName("A");
stu.setAge(10);
stu.setSex("man");
stu.setAddress("CHN");
//1、序列化:创建对象输出流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
//2、写入序列化对象
out.writeObject(stu);
stu.setSchool("BJDX");
//3、创建对象输入流
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream in = new ObjectInputStream(bis);
//4、对取对象
Student obj = (Student)in.readObject();
System.out.println(obj);
}
运行结果:
Student{name='A', age=10, sex='man', address='null', school='BJDX'}
Process finished with exit code 0
结果中的school变成了修改后的值,验证了刚才的说法。
那么问题来了,被transient修改是变量一定不能序列化吗?当然不是,只要实现了Externalizable接口,就可以实现
ublic class Student implements Externalizable {
private String name;
private int age;
private String sex;
private transient String address;
public static String school = "QHDX";
/**
* Getter method for property name.
*
* @return property value of name
*/
public String getName() {
return name;
}
/**
* Setter method for property name
*
* @param name value to be assigned to property name
*/
public void setName(String name) {
this.name = name;
}
/**
* Getter method for property age.
*
* @return property value of age
*/
public int getAge() {
return age;
}
/**
* Setter method for property age
*
* @param age value to be assigned to property age
*/
public void setAge(int age) {
this.age = age;
}
/**
* Getter method for property sex.
*
* @return property value of sex
*/
public String getSex() {
return sex;
}
/**
* Setter method for property sex
*
* @param sex value to be assigned to property sex
*/
public void setSex(String sex) {
this.sex = sex;
}
/**
* Getter method for property address.
*
* @return property value of address
*/
public String getAddress() {
return address;
}
/**
* Setter method for property address
*
* @param address value to be assigned to property address
*/
public void setAddress(String address) {
this.address = address;
}
/**
* Getter method for property school.
*
* @return property value of school
*/
public String getSchool() {
return school;
}
/**
* Setter method for property school
*
* @param school value to be assigned to property school
*/
public void setSchool(String school) {
Student.school = school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
", school='" + school + '\'' +
'}';
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
out.writeObject(sex);
out.writeObject(address);
System.out.println("自定义序列化信息");
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String)in.readObject();
age = in.readInt();
sex= (String)in.readObject();
address = (String)in.readObject();
System.out.println("自定义反序列化");
}
}
特别注意:
- 如果方法writeExternal和readExternal什么都不做处理,那么对象是不会被序列化的
- 需要序列化的属性,必须在writeExternal手工处理
- 反序列化时需要在readExternal中逐个获取属性值
- 反序列化readExternal中属性的顺序必须和序列化writeExternal中的顺序一致,否则会报错,这点很重要
结果如下
自定义序列化信息
自定义反序列化
Student{name='A', age=10, sex='man', address='CHN', school='QHDX'}
Process finished with exit code 0
可以看到 address变量也被序列化了
使用Externalizable进行序列化,当读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。这就是为什么输出结果中会显示调动了无参构造器。由于这个原因,实现Externalizable接口的类必须要提供一个无参的构造器,且它的访问权限为public。