Java序列化
1.什么是序列化?
把Java对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为Java对象的过程称为对象的反序列化。
对象的序列化主要有如下2种用途:
- 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中
- 在网络上传送对象的字节序列
2.序列化的实现方式
- 类继承Serializable接口,可使用writeObject方法和readObject方法控制类序列化的过程。 默认情况下只会初始化化非transient得实例变量。 一般使用writeObject方法和readObject方法来优化序列化的过程或者对敏感数据进行特殊的处理。
package com.wilian.serialize;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
*
* @author wilian
*
*/
//通过实现Serializable接口实现序列化
public class Comsumer implements Serializable {
private static final long serialVersionUID = -6701934960639960628L;
public static int MAX_BILL =2000;
private String name;
private Date birthday;
private transient String password;
private List<Order> orders;
static{
//反序列化不会调用静态初始化块
System.out.println("静态初始化块");
}
public Comsumer() {
//反序列化不会构造方法
System.out.println("正在创建一个Comsumer");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public List<Order> getOrders() {
return orders;
}
public void setOrders(List<Order> orders) {
this.orders = orders;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
private void writeObject(ObjectOutputStream out) throws IOException{
out.defaultWriteObject();
out.writeObject(this.password);
}
private void readObject(ObjectInputStream in)throws IOException, ClassNotFoundException{
in.defaultReadObject();
this.password=(String)in.readObject();
}
}
package com.wilian.serialize;
import java.io.Serializable;
//通过实现Serializable接口实现序列化
public class Order implements Serializable{
private static final long serialVersionUID = -4383674802849314614L;
private String orderNo;
private int money;
public String getOrderNo() {
return orderNo;
}
public void setOrderNo(String orderNo) {
this.orderNo = orderNo;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
package com.wilian.serialize;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class SerializedTest {
public static void main(String[] args) {
ObjectOutputStream oos =null;
ObjectInputStream ois =null;
try {
FileOutputStream fos = new FileOutputStream(
new File("D://cumsumer.dat"));
oos = new ObjectOutputStream(fos);
Comsumer comsumer= new Comsumer();
comsumer.setBirthday(new Date());
comsumer.setName("wilian");
comsumer.setPassword("12345678");
List<Order> orders=new ArrayList<Order>();
Order order1 = new Order();
order1.setMoney(15);
order1.setOrderNo("001");
orders.add(order1);
Order order2 = new Order();
order2.setMoney(19);
order2.setOrderNo("002");
orders.add(order2);
comsumer.setOrders(orders);
oos.writeObject(comsumer);
FileInputStream fis = new FileInputStream(
new File("D://cumsumer.dat"));
ois=new ObjectInputStream(fis);
Comsumer c =(Comsumer) ois.readObject();
//验证两个对象是否相等
System.out.println(comsumer==c);
System.out.println(comsumer.equals(c));
//
System.out.println(c.getName());
System.out.println(c.getBirthday());
System.out.println(c.getPassword());
System.out.println(c.getOrders().size());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally{
if(oos!=null)
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
if(ois!=null)
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 实现Externalizable接口;Externalizable接口继承自Serializable接口,可以完全控制类的序列化行为。与Serializable另外个不同之处:一个类如果实现了Externalizable接口,那么它必须具有public类型的不带参数的构造方法,否则这个类无法反序列化。
package com.wilian.serialize;
import java.io.Externalizable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.Date;
public class Dog implements Externalizable {
protected String name;
protected int weight;
protected Date birthday;
public Dog(String name, int weight, Date birthday) {
this.name = name;
this.weight = weight;
this.birthday = birthday;
}
//要提供public无参构造方法否则会抛出如下异常
//java.io.InvalidClassException: com.wilian.serialize.Dog; no valid constructor
public Dog(){};
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(weight);
out.writeObject(birthday);
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
name=(String)in.readObject();
weight=in.readInt();
birthday=(Date)in.readObject();
}
public static void main(String[] args) {
try {
FileOutputStream fos = new FileOutputStream(
new File("D://dog.dat"));
ObjectOutputStream oos = new ObjectOutputStream(fos);
Dog dog=new Dog("Wangcai",25,new Date());
oos.writeObject(dog);
oos.close();
FileInputStream fis = new FileInputStream(
new File("D://dog.dat"));
ObjectInputStream ois = new ObjectInputStream(fis);
Dog dog1 =(Dog) ois.readObject();
ois.close();
System.out.println(dog1.name);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
3.序列化中readResolve()方法的应用
由上面的例子可以看出序列化出来的对象与序列化之前的对象非同一个对象,如果在有些单例的场景这样就违反了JVM中只有一个实例的约定。如果一个类提供了readResolve方法,那么在执行反序列化操作时,先按照默认方式或者用户自定义的方式进行反序列化,最后再调用readResolve方法,该方法返回的对象为反序列化的最终结果。
package com.wilian.serialize;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
public class SystemConfig implements Serializable {
/**
*
*/
private static final long serialVersionUID = 4026591699359366802L;
private static final SystemConfig instance =new SystemConfig();
private String ip;
private String host;
private int port;
private SystemConfig(){
this.ip="127.0.0.1";
this.host="localhost";
this.port=8080;
}
public static SystemConfig getInstance(){
return instance;
}
public String getIp() {
return ip;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
/**
* 返回值必须是Object,否则函数不生效
* 方法权限可以是private\默认\protected级别
* @return Object
* @throws ObjectStreamException
*/
private Object readResolve() throws ObjectStreamException{
return instance;
}
public static void main(String[] args) {
try {
SystemConfig config=SystemConfig.getInstance();
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(buf);
oos.writeObject(config);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));
SystemConfig config1 = (SystemConfig)ois.readObject();
System.out.println(config1==config);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
4.serialVersion序列化版本的作用
serialVersionUID有两种用途:
- 希望不同版本对序列化兼容,因此需要确保类的不同版本具有相同的seriaVersionUID;
- 不希望不同版本对序列化兼容,因此需要确保类的不同版本具有不同的seriaVersionUID;
当一个类的不同版本的seriaVersionUID相同,仍然有可能出现序列化不兼容的情况。