1)、什么是序列化?
我们的对象不只是存在内存中,还需要传输网络,或者保存起来下次再加载出来用,所以需要Java序列化技术
Java序列化技术正是将对象转变为一串由二进制字节组成的数组,可以通过将二进制数据保存到磁盘或者传输网络,磁盘或者网络接收者可以在对象的属类的模板上来反序列化类的对象,达到对象序列化的目的
2)、序列化ID
1)序列化ID的作用
序列化ID决定着是否能够成功反序列化。Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常
2)序列化ID如何产生
当我们一个实体类中没有显示的定义一个名为“serialVersionUID”、类型为long的变量时,Java序列化机制会根据编译时的class自动生成一个serialVersionUID作为序列化版本比较,这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID。比如,当我们编写一个类时,随着时间的推移,我们因为需求改动,需要在本地类中添加其他的字段,这个时候再反序列化时便会出现serialVersionUID不一致,导致反序列化失败。解决的方式是通过在本地类中添加一个“serialVersionUID”变量,值保持不变,便可以进行序列化和反序列化
3)、怎么序列化一个对象?
要序列化一个对象,这个对象所在类就必须实现Java序列化的接口:java.io.Serializable
1)类添加序列化接口
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private String address;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [username=" + username + ", address=" + address + "]";
}
}
2)可以借助commons-lang3工具包里面的类实现对象的序列化及反序列化
添加依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
案例一:序列化对象字节到内存然后反序列化
public class Test {
public static void main(String[] args) {
User user = new User();
user.setUsername("Java");
user.setAddress("China");
System.out.println("序列化前");
System.out.println(user);
byte[] bytes = SerializationUtils.serialize(user);
user= SerializationUtils.deserialize(bytes);
System.out.println("序列化后");
System.out.println(user);
}
}
运行结果:
User [username=Java, address=China]
案例二:序列化到磁盘然后再反序列化
public class Test2 {
public static void main(String[] args) throws FileNotFoundException {
User user = new User();
user.setUsername("Java");
user.setAddress("China");
File file = new File("D:/test.txt");
OutputStream outputStream = new FileOutputStream(file);
SerializationUtils.serialize(user, outputStream);
InputStream inputStream = new FileInputStream(file);
user = SerializationUtils.deserialize(inputStream);
System.out.println(user);
}
}
运行结果同上
3)使用ObjectInputStream和ObjectOutputStream实现
public class Test3 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
User user = new User();
user.setUsername("Java");
user.setAddress("China");
File file = new File("D:/test.txt");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(user);
oos.flush();
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
user = (User) ois.readObject();
ois.close();
System.out.println(user);
}
}
运行结果同上
4)、序列化注意事项
- 序列化对象必须实现序列化接口
- 序列化对象里面的属性是对象的话也要实现序列化接口
- 类的对象序列化后,类的序列化ID不能轻易修改,不然反序列化会失败
- 类的对象序列化后,类的属性有增加或者删除不会影响序列化,只是值会丢失
- 如果父类序列化了,子类会继承父类的序列化,子类无需添加序列化接口
- 如果父类没有序列化,子类序列化了,子类中的属性能正常序列化,但父类的属性会丢失,不能序列化
- 用Java序列化的二进制字节数据只能由Java反序列化,不能被其他语言反序列化
- 如果某个字段不想序列化,在该字段前加上transient关键字即可
5)、什么是transient?
被transient修饰的变量不能被序列化
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String address;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [username=" + username + ", address=" + address + "]";
}
}
public class Test {
public static void main(String[] args) {
User user = new User();
user.setUsername("Java");
user.setAddress("China");
System.out.println("序列化前");
System.out.println(user);
byte[] bytes = SerializationUtils.serialize(user);
User u = SerializationUtils.deserialize(bytes);
System.out.println("序列化后");
System.out.println(u);
}
}
6)、静态变量能被序列化吗?
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
public static String address;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
@Override
public String toString() {
return "User [username=" + username + ", address=" + address + "]";
}
}
public class Test {
public static void main(String[] args) throws FileNotFoundException {
User user = new User();
user.setUsername("Java");
User.address = "China";
System.out.println("序列化前");
System.out.println(user);
File file = new File("D:/test.txt");
OutputStream outputStream = new FileOutputStream(file);
SerializationUtils.serialize(user, outputStream);
// 在反序列化出来之前,改变静态变量的值
User.address = "China2";
InputStream inputStream = new FileInputStream(file);
user = SerializationUtils.deserialize(inputStream);
System.out.println("序列化后");
System.out.println(user);
}
}
运行结果:
序列化前
User [username=Java, address=China]
序列化后
User [username=Java, address=China2]
把address改为了public static,并在反序列化出来之前改变了静态变量的值,结果可以看出序列化之后的值并非序列化进去时的值
静态变量不能被序列化,读取出来的是address在JVM内存中存储的值
静态变量不会序列化,主要是因为对象序列化只是对对象进行序列化,不是对类进行序列化,也就是static变量不会被序列化,是因为它不是对象的数据,而是类的数据
7)、transient真不能被序列化吗
public class User implements Externalizable {
private static final long serialVersionUID = 1L;
private String username;
private transient String address;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User [username=" + username + ", address=" + address + "]";
}
@Override
public void writeExternal(ObjectOutput objectOutput) throws IOException {
objectOutput.writeObject(address);
}
@Override
public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {
address = (String) objectInput.readObject();
}
}
public class Test {
public static void main(String[] args) {
User user = new User();
user.setUsername("Java");
user.setAddress("China");
System.out.println("序列化前");
System.out.println(user);
byte[] bytes = SerializationUtils.serialize(user);
user= SerializationUtils.deserialize(bytes);
System.out.println("序列化后");
System.out.println(user);
}
}
运行结果:
序列化前
User [username=Java, address=China]
序列化后
User [username=null, address=China]
address被transient修饰了,但是还能序列化出来,是因为User实现了接口Externalizable,而不是Serializable
在Java中有两种实现序列化的方式,Serializable和Externalizable
这两种序列化方式的区别在于:实现Serializable接口是自动序列化的,实现Externalizable则需要手动序列化,通过writeExternal和readExternal方法手动进行,这也是为什么上面的username为null的原因
8)、transient关键字总结
- transient修饰的变量不能被序列化
- transient只作用于实现Serializable接口
- transient只能用来修饰普通成员变量字段
- 不管有没有transient修饰,静态变量都不能被序列化