序列化和反序列化的本质是解决在进行远程通信和持久化数据时,如何保存和恢复数据的问题。
为什么要进行序列化和反序列化操作?
其实主要是为了持久化和进行传输,我们都知道在Java 中一些数据都是以对象的形式存在的,那如果我想把对象传输其他人怎么办?
那肯定是不能直接传输的,假如我们要进行网络传输,传输的过程中是不能传输Java 对象的,因为底层的实现并不知道Java 对象的存在,我们一般都是传输字节,所以我们需要能够进行传输的形式,序列化就是这个操作,我们将Java 对象序列化为一个字节数组,这样就可以进行传输了。
这个原理不仅在Java中,在所有需要进行远程通信和数据存储的编程语言中都存在。
为了在Java中实现序列化,我们需要完成以下两个步骤。
首先,让我们要序列化的类实现Serializable接口。下面是一个实现了Serializable接口的Person类。
public class Person implements Serializable {
private String name;
// getter 和 setter 方法省略
}
然后,我们可以使用java.io.ObjectOutputStream类将Person对象写入到硬盘中,如下所示:
Person person = new Person();
person.setName("Grace");
try (FileOutputStream fos = new FileOutputStream("person.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos)) {
oos.writeObject(person);
} catch (IOException e) {
e.printStackTrace();
}
反序列化就是将序列化的字节流恢复成原来的对象。我们可以通过java.io.ObjectInputStream类来完成这个过程,如下所示:
try (FileInputStream fis = new FileInputStream("person.obj");
ObjectInputStream ois = new ObjectInputStream(fis)) {
Person person = (Person) ois.readObject();
System.out.println(person.getName());
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
序列化并不总是那么简单。我们可能会遇到版本控制问题、子类序列化,以及某些字段不应该被序列化的问题。
版本控制问题: 在序列化对象中,serialVersionUID是JAVA为了有效地实现序列化所引入的。serialVersionUID作为版本控制,在反序列化过程中,JAVA虚拟机会根据版本号确保序列化类的一致性。如果对象的版本号不一致,反序列化过程就会失败,抛出InvalidClassException异常。示例:
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
// 其他代码省略
}
子类序列化: 在Java中,父类通过实现Serializable接口,子类可以由此自动实现序列化,反之则不行。当子类需要序列化,而父类并未实现序列化接口时,可以通过在子类添加一个父类的构造器来解决。这样的示例:
public class Person {
private String name;
Person(String name){
this.name = name;
// 其他代码省略
}
}
public class Employee extends Person implements Serializable {
private int employeeId;
Employee(String name, int employeeId) {
super(name);
this.employeeId = employeeId;
// 其他代码省略
}
}
transient关键字: 在序列化过程中,可能某些字段并不需要序列化,比如说一些敏感信息(如密码、银行卡号等)。这时可以使用transient关键字标记不需要序列化的字段。值得注意的是,被transient关键字标记的字段,在反序列化回来后将为null。示例:
public class Person implements Serializable {
private String name;
private transient String password; // 此字段不会被序列化
// getter 和 setter 方法省略
}
有时候,我们可能想要更多地控制序列化的过程。Java为我们提供了自定义序列化的方法——通过在我们的类中重写writeObject(ObjectOutputStream out)和readObject(ObjectInputStream in)方法,我们就可以控制对象的序列化过程。示例:
public class Person implements Serializable {
private String name;
private transient String password; // 此字段不会被序列化
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeObject(password);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
password = (String) in.readObject();
}
// getter 和 setter 方法省略
}
Serializable
接口,那么将无法通过ObjectOutputStream
进行序列化。static
的变量属于类级别的,序列化只处理对象的状态,即类的非静态字段。静态变量的生命周期和对象不同,由JVM负责维护。transient
关键字。被transient
修饰的字段在序列化时将被忽略,反序列化时按照字段类型的默认值填充。序列化被广泛应用于各种场景,如远程调用(RMI或EJB),持久化(Hibernate),HTTP会话在集群节点间的复制等。理解序列化及反序列化的原理和实现,对掌握Java及各种Java框架和中间件至关重要。
下面是序列化和反序列化常见应用场景:
序列化和反序列化看似简单的概念,但实则包含了许多复杂的细节。希望通过这篇文章,能够帮助大家更深入地理解序列化和反序列化的过程,以及如何在实际编程中使用序列化。
如文章有任何疑问,欢迎提出!
欢迎大家访问我的个人博客 无限进步的博客