简单的讲,序列化就是将java对象转化成二进制保存到磁盘中去,反序列化就是从磁盘中读取文件流而后转成java对象。java
JDK提供了下面两种方式实现序列化:安全
Serializable
接口Externalizable
下面分别实例演示两种实现方式: 假设本文全部的序列化对象为User
,其拥有下面属性:bash
/**
* 序列化對象
*
* @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;
复制代码
序列化和反序列化代码为:ide
// 序列化方法
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();
}
复制代码
输出以下:ui
广东深圳
hjw
复制代码
User
实现Serializable
接口是必须的吗?是的,是必须的,不然报下面异常:spa
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
的设计语义code
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
接口,后者容许咱们自定义序列化规则