Java序列化魔术方法及其示例使用

在上一篇文章中, 您需要了解有关Java序列化的所有知识 ,我们讨论了如何通过实现Java序列化来启用类的可序列化性。
Serializable接口。 如果我们的类未实现Serializable接口,或者该类具有对非Serializable类的引用,则JVM将抛出NotSerializableException

可序列化类的所有子类型本身都是可序列化的,并且
Externalizable接口还扩展了可序列化。 所以即使我们 使用Externalizable自定义序列化过程,我们的类仍然是 Serializable

Serializable接口是一个没有方法或字段的标记接口,它的作用类似于JVM的标志。 ObjectInputStreamObjectOutputStream类提供的Java序列化过程完全由JVM控制。

但是,如果我们想添加一些其他逻辑来增强此正常过程,例如,我们可能希望在对敏感信息进行序列化/反序列化之前对其进行加密/解密。 Java为此提供了一些其他方法,我们将在此博客中讨论。

writeObject和readObject方法

希望自定义或添加一些其他逻辑以增强常规序列化/反序列化过程的可序列化类应提供
具有以下确切签名的writeObjectreadObject方法:

  • private void writeObject(java.io.ObjectOutputStream out) throws IOException
  • private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException

Java序列化您需要了解的一切文章下,已经对这些方法进行了详细讨论。

readObjectNoData方法

Serializable类的Java文档所述,如果我们要在序列化流未将给定类列出为要反序列化的对象的超类的情况下初始化其特定类的对象状态,则应提供writeObject和具有以下确切签名的readObject方法:

  • private void readObjectNoData() throws ObjectStreamException

在接收方使用与发送方不同的反序列化实例类的版本,并且接收方的版本扩展了发送方的版本未扩展的类的情况下,可能会发生这种情况。 如果序列化流已被篡改,也会发生这种情况。 因此,尽管源流“充满敌意”或不完整,但readObjectNoData对于正确初始化反序列化的对象很有用。

每个可序列化的类都可以定义自己的readObjectNoData方法。 如果可序列化的类未定义readObjectNoData方法,则在上述情况下,该类的字段将被初始化为其默认值。

writeReplace和readResolve方法

将对象写入流时需要指定要使用的替代对象的可序列化类应为此特殊方法提供确切的签名:

  • ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException

当从流中读取实例时,需要指定替换的Serializable类应为此特殊方法提供确切的签名:

  • ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException

基本上, writeReplace方法允许开发人员提供将被序列化的替换对象,而不是原始对象。 在反序列化过程中使用了readResolve方法,用我们选择的另一个方法来替换反序列化的对象。

writeReplace和readResolve方法的主要用途之一是使用Serialized类实现单例设计模式。 我们知道, 反序列化过程每次都会创建一个新对象 ,它也可以用作深度克隆对象的方法,如果我们必须使类为单例,那么这样做就不好了。


您可以在Java CloningJava上阅读有关Java克隆和序列化的更多信息。
Java序列化主题。

readObject返回之后调用readResolve方法(相反,在writeObject之前(可能在另一个对象上)调用writeReplace )。 该对象在方法返回替换this返回到的用户对象ObjectInputStream.readObject并流中的对象中的任何进一步的反向引用。 我们可以使用writeReplace方法将序列化对象替换为null,以便不进行序列化,然后使用readResolve方法将反序列化的对象替换为单例实例。

validateObject方法

如果我们想在某些字段上执行某些验证,则可以通过实现ObjectInputValidation接口并重写
来自它的validateObject方法。

当我们通过从readObject方法调用ObjectInputStream.registerValidation(this, 0)注册此验证时,将自动调用validateObject方法。 在将数据流交还给您的应用程序之前,验证数据流是否受到篡改或数据有意义是非常有用的。

下面的示例涵盖了上述所有方法的代码

 public class SerializationMethodsExample { 
     public static void main(String[] args) throws IOException, ClassNotFoundException { 
         Employee emp = new Employee( "Naresh Joshi" , 25 ); 
         System.out.println( "Object before serialization: " + emp.toString()); 
         // Serialization 
         serialize(emp); 
         // Deserialization 
         Employee deserialisedEmp = deserialize(); 
         System.out.println( "Object after deserialization: " + deserialisedEmp.toString()); 
         System.out.println(); 
         // This will print false because both object are separate 
         System.out.println(emp == deserialisedEmp); 
         System.out.println(); 
         // This will print false because both `deserialisedEmp` and `emp` are pointing to same object, 
         // Because we replaced de-serializing object in readResolve method by current instance 
         System.out.println(Objects.equals(emp, deserialisedEmp)); 
     } 
     // 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(); 
         } 
     }  }  Employee class implements Serializable, ObjectInputValidation { 
     private static final long serialVersionUID = 2L; 
     private String name; 
     private int age; 
     public Employee(String name, int age) { 
         this .name = name; 
         this .age = age; 
     } 
     // With ObjectInputValidation interface we get a validateObject method where we can do our validations. 
     @Override 
     public void validateObject() { 
         System.out.println( "Validating age." ); 
         if (age < 18 || age > 70 ) 
         { 
             throw new IllegalArgumentException( "Not a valid age to create an employee" ); 
         } 
     } 
     // Custom serialization logic, 
     // This will allow us to have additional serialization logic on top of the default one eg encrypting object before serialization. 
     private void writeObject(ObjectOutputStream oos) throws IOException { 
         System.out.println( "Custom serialization logic invoked." ); 
         oos.defaultWriteObject(); // Calling the default serialization logic 
     } 
     // Replacing de-serializing object with this, 
     private Object writeReplace() throws ObjectStreamException { 
         System.out.println( "Replacing serialising object by this." ); 
         return this ; 
     } 
     // Custom deserialization logic 
     // This will allow us to have additional deserialization logic on top of the default one eg performing validations, decrypting object after deserialization. 
     private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { 
         System.out.println( "Custom deserialization logic invoked." ); 
         ois.registerValidation( this , 0 ); // Registering validations, So our validateObject method can be called. 
         ois.defaultReadObject(); // Calling the default deserialization logic. 
     } 
     // Replacing de-serializing object with this, 
     // It will will not give us a full proof singleton but it will stop new object creation by deserialization. 
     private Object readResolve() throws ObjectStreamException { 
         System.out.println( "Replacing de-serializing object by this." ); 
         return this ; 
     } 
     @Override 
     public String toString() { 
         return String.format( "Employee {name='%s', age='%s'}" , name, age); 
     }  } 

您可以在此找到本文的完整源代码。
Github存储库 ,请随时提供宝贵的反馈。

翻译自: https://www.javacodegeeks.com/2019/09/java-serialization-magic-methods-and-their-uses-with-example.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值