简单的讲,序列化就是将java对象转化成二进制保存到磁盘中去,反序列化就是从磁盘中读取文件流然后转成java对象。
JDK提供了下面两种方式实现序列化:
Serializable Externalizable
下面分别实例演示两种实现方式: 假设本文所有的序列化对象为 User
,其拥有下面属性:
/** * 序列化對象 * * @Author jiawei huang * @Since 2020年1月2日 * @Version 1.0 */ public class User { private String userName; private String address; // ....setter/getter } 复制代码
我们在上面 User
对象的基础上实现 Serializable
接口,代码如下:
public class User implements Serializable { // 序列化ID private static final long serialVersionUID = 1L; 复制代码
序列化和反序列化代码为:
// 序列化方法 public static void serialize(User user) { ObjectOutputStream outputStream = null; try { outputStream = new ObjectOutputStream(new FileOutputStream("C://Users//Administrator//Desktop//data.txt")); outputStream.writeObject(user); } catch (Exception e) { e.printStackTrace(); } finally { // close stream if (outputStream != null) { try { outputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } } // 反序列化方法 public static void deserialize() { ObjectInputStream objectInputStream = null; try { objectInputStream = new ObjectInputStream( new FileInputStream("C://Users//Administrator//Desktop//data.txt")); User user = (User) objectInputStream.readObject(); System.out.println(user.getAddress()); System.out.println(user.getUserName()); } catch (Exception e) { // error e.printStackTrace(); } finally { // close stream if (objectInputStream != null) { try { objectInputStream.close(); } catch (IOException e) { // error e.printStackTrace(); } } } } 复制代码
测试代码如下:
public static void main(String[] args) { User user = new User(); user.setAddress("广东深圳"); user.setUserName("hjw"); serialize(user); deserialize(); } 复制代码
输出如下:
广东深圳 hjw 复制代码
User
实现 Serializable
接口是必须的吗? 是的,是必须的,否则报下面异常:
java.io.NotSerializableException: ex.serializable.User at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348) at ex.serializable.Main.serialize(Main.java:41) at ex.serializable.Main.main(Main.java:33) 复制代码
因为在 ObjectOutputStream
中执行了如下代码限制:
这也说明,jdk并没有默认支持对象的序列化,为什么默认不支持呢?因为java的安全机制限制,我们设想一下,假设对象都默认支持序列化,那么就像上面那个 User
对象,其私有 private
修饰属性也被序列化了,那么不符合 private
的设计语义
Externalizable
接口继承自 Serializable
接口 Externalizable
接口允许我们自定义对象属性的序列化 Externalizable
接口必须重写 writeExternal
和 readExternal
方法 我们重新新建一个 User1
对象如下:
public class User1 implements Externalizable { private String userName; private String address; // setter、getter @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(userName); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { System.out.println(in.readObject()); } } 复制代码
测试代码如下:
User1 user1 = new User1(); user1.setAddress("广东深圳"); user1.setUserName("hjw"); user1.writeExternal( new ObjectOutputStream(new FileOutputStream("C://Users//Administrator//Desktop//data.txt"))); user1.readExternal(new ObjectInputStream(new FileInputStream("C://Users//Administrator//Desktop//data.txt"))); 复制代码
输出如下:
hjw 复制代码
两种实现方式的区别在于 Externalizable
允许我们自定义序列化规则, Externalizable
接口会初始化一次无参构造器,而 Serializable
不会
通过实现 Externalizable
接口我们可以自定义序列化的属性,同样的道理,关键字 transient
也可以达到相同的效果,但是两者之间还是有一些区别的。
transient
修饰的变量,即使使用 private
修饰也会被序列化 private
属性不被序列化,则可以使用 Externalizable
static
修饰的成员变量属于类全局属性,其值在JDK1.8存储于元数据区(1.8以前叫方法区),不属于对象范围,将不会被序列化。
下面验证 static
不会被序列化: 修改 User
对象 userName
属性为 static
修饰
User user = new User(); user.setAddress("广东深圳"); user.setUserName("hjw"); serialize(user); user.setUserName("mike"); deserialize(); 复制代码
输出如下:
广东深圳 mike 复制代码
我们将 hjw
序列化到了磁盘文件,结果反序列化之后得到的值却是 mike
java能否反序列化成功,取决于 serialVersionUID
是否一致,我们可以把它理解成为版本号,一旦版本号不一致,将会报序列化出错。我们可以为对象声明一个自定义 serialVersionUID
,也可以使用默认的 1L
。
总结就是:
当我们新增一个类实现 Serializable
接口时,建议我们为其新增一个 serialVersionUID
,因为假设我们没有声明 serialVersionUID
,那么后面假设我们修改了该类的接口(新增字段)时,当我们再次反序列化时,就会报错。因为java会拿编译器根据类信息自动生成一个id1和反序列化得到的id2进行比较,如果类有改动,id2和id1肯定不一致啦。
Q1:既然 static
修饰的不会被序列化,而java又是如何通过 serialVersionUID
进行比较的呢?
serialVersionUID
应该是一个特殊字段可以被序列化,应该可以从源码中找到答案,小编没找到,欢迎评论区留言。
深度克隆即把引用类型的成员也给克隆了,基于上面例子,我们新增一个类 Car
,并且 User
拥有一个 Car
类型对象。
public class Car implements Serializable { private static final long serialVersionUID = 1L; private String carName; private int price; // ...... } 复制代码
public class User implements Serializable { private static final long serialVersionUID = 1L; private String userName; // private Car car; private String address; @Override public String toString() { return "User [userName=" + userName + ", carName=[" + car.getCarName() + "],price=[" + car.getPrice() + "]" + ", address=" + address + "]"; } // ...... } 复制代码
克隆方法
public static <T extends Serializable> T cloneObject(T obj) throws IOException { ObjectOutputStream oos = null; ByteArrayOutputStream baos = null; byte[] bytes = null; try { // 序列化 baos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(baos); oos.writeObject(obj); bytes = baos.toByteArray(); } catch (Exception e) { e.printStackTrace(); } finally { if (baos != null) { baos.close(); } if (oos != null) { oos.close(); } } ByteArrayInputStream bais = null; ObjectInputStream ois = null; try { // 反序列化 bais = new ByteArrayInputStream(bytes); ois = new ObjectInputStream(bais); return (T) ois.readObject(); } catch (Exception e) { e.printStackTrace(); } finally { if (bais != null) { baos.close(); } if (oos != null) { ois.close(); } } return null; } 复制代码
测试代码及输出如下:
User user = new User(); user.setAddress("广东深圳"); user.setUserName("hjw"); Car car = new Car(); car.setCarName("单车"); car.setPrice(300); user.setCar(car); User clonedUser = cloneObject(user); System.out.println(clonedUser); 复制代码
User [userName=hjw, carName=[单车],price=[300], address=广东深圳] 复制代码
Serializable
接口,另一种是实现 Externalizable
接口,后者允许我们自定义序列化规则