java自定义外部接口
在上一篇文章“用示例介绍的有关Java序列化的一切”中 ,我解释了如何使用以下方法序列化/反序列化一个对象
Serializable
接口,还说明了如何使用writeObject
和readObject
方法自定义序列化过程。
Java序列化过程的缺点
但是这些自定义是不够的,因为JVM可以完全控制序列化过程,而这些自定义逻辑只是默认序列化过程的补充。 我们仍然必须通过从writeObject
和ObjectInputStream.defaultReadObject()
调用ObjectOutputStream.defaultWriteObject()
和ObjectInputStream.defaultReadObject()
来使用默认的序列化逻辑。
readObject
方法。 而且,如果不调用这些默认方法,我们的对象将不会被序列化/反序列化。
默认的序列化过程是完全递归的。 因此,每当我们尝试序列化一个对象时,序列化过程就会尝试使用我们的类( static
和static
除外)对所有字段(原始和引用)进行序列化。
transient
场)。 这使得序列化过程非常缓慢。
现在,假设我们有一个对象,其中包含许多字段,由于某些原因,我们不想序列化这些字段(这些字段将始终分配有默认值)。 使用默认的序列化过程,我们将必须使所有这些字段都是瞬态的,但是它仍然不会高效,因为将进行大量检查以查看这些字段是否为瞬态的。
因此,如我们所见,使用默认序列化过程有很多弊端,例如:
- 序列化的定制是不够的,因为JVM可以完全控制序列化过程,而我们的定制逻辑只是默认序列化过程的补充。
- 默认序列化过程是完全递归且缓慢的。
- 为了不对字段进行序列化,我们必须将其声明为瞬态,大量瞬态字段将再次使过程变慢。
- 我们无法控制如何对字段进行序列化和反序列化。
- 默认序列化过程在创建对象时不会调用构造函数,因此它无法调用构造函数提供的初始化逻辑。
什么是外部化和外部化接口
正如我们在上面看到的那样,默认的Java序列化效率不高。 我们可以通过使用Externalizable
接口而不是
Serializable
接口。
我们可以通过实现
可外部化的接口并覆盖它的方法writeExternal()
和
readExternal()
。 但是使用这种方法,我们将无法从JVM获得任何类型的默认序列化逻辑,而是由我们来提供完整的序列化和反序列化逻辑。
因此,非常仔细地对测试这些方法进行编码非常必要,因为这可能会破坏序列化过程。 但是,如果正确实现,与默认序列化过程相比,外部化过程非常快。
我们将以下面的Employee
类对象为例进行说明:
// Using Externalizable, complete serialization/deserialization logic becomes our responsibility, // We need to tell what to serialize using writeExternal() method and what to deserialize using readExternal(), // We can even serialize/deserialize static and transient variables, // With implementation of writeExternal() and readExternal(), methods writeObject() and readObject() becomes redundant and they do not get called. Employee class implements Externalizable {
// This serialVersionUID field is necessary for Serializable as well as Externalizable to provide version control,
// Compiler will provide this field if we do not provide it which might change if we modify class structure of our class, and we will get InvalidClassException,
// If we provide a value to this field and do not change it, serialization-deserialization will not fail if we change our class structure.
private static final long serialVersionUID = 2L;
private String firstName;
private transient String lastName; // Using Externalizable, we can even serialize/deserialize transient variables, so declaring fields transient becomes unnecessary.
private int age;
private static String department; // Using Externalizable, we can even serialize/deserialize static variables according to our need.
// Mandatory to have to make our class Externalizable
// When an Externalizable object is reconstructed, the object is created using public no-arg constructor before the readExternal method is called.
// If a public no-arg constructor is not present then a InvalidClassException is thrown at runtime.
public Employee() {
}
// All-arg constructor to create objects manually
public Employee(String firstName, String lastName, int age, String department) {
this .firstName = firstName;
this .lastName = lastName;
this .age = age;
Employee.department = department;
validateAge();
}
private void validateAge() {
System.out.println( "Validating age." );
if (age < 18 || age > 70 ) {
throw new IllegalArgumentException( "Not a valid age to create an employee" );
}
}
@Override
// We need to tell what to serialize in writeExternal() method
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println( "Custom externalizable serialization logic invoked." );
out.writeUTF(firstName);
out.writeUTF(lastName);
out.writeInt(age);
out.writeUTF(department);
}
@Override
// We need to tell what to deserialize in readExternal() method
// The readExternal method must read the values in the same sequence and with the same types as were written by writeExternal
public void readExternal(ObjectInput in) throws IOException {
System.out.println( "Custom externalizable serialization logic invoked." );
firstName = in.readUTF();
lastName = in.readUTF();
age = in.readInt();
department = in.readUTF();
validateAge();
}
@Override
public String toString() {
return String.format( "Employee {firstName='%s', lastName='%s', age='%s', department='%s'}" , firstName, lastName, age, department);
}
// Custom serialization logic, It will be called only if we have implemented Serializable instead of Externalizable.
private void writeObject(ObjectOutputStream oos) throws IOException {
System.out.println( "Custom serialization logic invoked." );
}
// Custom deserialization logic, It will be called only if we have implemented Serializable instead of Externalizable.
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
System.out.println( "Custom deserialization logic invoked." );
} }
序列化如何与可外部化接口一起使用
如上面在示例Employee
类中所看到的,我们可以通过实现Externalizable接口并覆盖其方法writeExternal()
和readExternal()
来编写自己的序列化逻辑。
通过调用DataOutput的原始值对象或调用ObjectOutput的writeObject方法对象,字符串和数组,对象可以实现writeExternal方法来保存其内容。
该对象可以实现readExternal方法来恢复其内容,方法是为原始类型调用DataInput的方法,为对象,字符串和数组调用readObject的方法。 readExternal方法必须按与writeExternal相同的顺序和相同的类型读取值。
// We need to tell what fields to serialize in writeExternal() method public void writeExternal(ObjectOutput out) throws IOException {
System.out.println( "Custom externalizable serialization logic invoked." );
out.writeUTF(firstName);
out.writeUTF(lastName);
out.writeInt(age);
out.writeUTF(department); } // We need to tell what fields to deserialize in readExternal() method // The readExternal method must read the values in the same sequence and with the same types as were written by writeExternal public void readExternal(ObjectInput in) throws IOException {
System.out.println( "Custom externalizable serialization logic invoked." );
firstName = in.readUTF();
lastName = in.readUTF();
age = in.readInt();
department = in.readUTF();
validateAge(); }
要将对象序列化和反序列化为文件,我们需要遵循与Serializable示例相同的过程,这意味着调用
如以下代码所示,完成ObjectOutputStream.writeObject()
和ObjectInputStream.readObject()
:
public class ExternalizableExample {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Employee empObj = new Employee( "Shanti" , "Sharma" , 25 , "IT" );
System.out.println( "Object before serialization => " + empObj.toString());
// Serialization
serialize(empObj);
// Deserialization
Employee deserializedEmpObj = deserialize();
System.out.println( "Object after deserialization => " + deserializedEmpObj.toString());
}
// Serialization code
static void serialize(Employee empObj) throws IOException {
try (FileOutputStream fos = new FileOutputStream( "data.obj" );
ObjectOutputStream oos = new ObjectOutputStream(fos))
{
oos.writeObject(empObj);
}
}
// Deserialization code
static Employee deserialize() throws IOException, ClassNotFoundException {
try (FileInputStream fis = new FileInputStream( "data.obj" );
ObjectInputStream ois = new ObjectInputStream(fis))
{
return (Employee) ois.readObject();
}
} }
Externalizable
接口是Serializable
的子接口,即
Externalizable extends Serializable
。 因此,如果我们实现Externalizable
接口并覆盖其writeExternal()
和
然后,将使用readExternal()
方法优先于这些方法,而不是由JVM提供的默认序列化机制。 这些方法取代了writeObject
和readObject
方法的自定义实现,因此,如果我们还提供writeObject()
和readObject()
,则将忽略它们。
在序列化过程中,将针对要序列化的每个对象的Externalizable接口进行测试。 如果对象支持Externalizable,则调用writeExternal方法。 如果对象不支持Externalizable并且实现了Serializable,则使用ObjectOutputStream保存该对象。
重建Externalizable对象时,将使用公共的无参数构造函数创建一个实例,然后调用readExternal方法。 可序列化的对象通过从ObjectInputStream读取来恢复。
- 重建Externizable对象时,在调用readExternal方法之前,使用公共的无参数构造函数创建对象。 如果不存在公共的无参数构造函数,则在运行时引发InvalidClassException。
- 使用Externalizable,我们甚至可以序列化/反序列化瞬态变量,因此无需声明瞬态字段。
- 使用Externalizable,我们甚至可以根据需要对静态变量进行序列化/反序列化。
Externalizable实例可以通过Serializable接口中记录的writeReplace和readResolve方法指定替换对象。
Java 序列化还可以用于深度克隆对象 。 Java克隆是Java社区中最有争议的话题,它的确有其缺点,但是在对象完全满足Java克隆的强制条件之前,它仍然是创建对象副本的最流行和最简单的方法。 我在长达3篇文章的Java克隆系列中详细介绍了克隆 ,其中包括Java克隆和克隆类型(浅和深)等文章, 并带有示例 , Java克隆–复制构造器与克隆 , Java克隆–甚至复制构造器都不一样如果您想了解更多有关克隆的知识,请充分阅读它们。
可外部化与可序列化之间的差异
让我们列出Java中Externalizable接口和Serializable接口之间的主要区别。
您可以在此找到本文的完整源代码。
Github存储库 ,请随时提供宝贵的反馈。
java自定义外部接口