手机网站制作架构,wordpress主题安装500,网站接入支付宝需要网站备案吗,怎么做免费网页在上一篇文章中#xff0c;我们介绍了在Java中创建对象的5种不同方法 #xff0c;我解释了如何对序列化对象进行反序列化以创建新对象#xff0c;并且在此博客中#xff0c;我将详细讨论序列化和反序列化。 我们将以下面的Employee类对象为例进行说明 // If we use Serial… 在上一篇文章中我们介绍了在Java中创建对象的5种不同方法 我解释了如何对序列化对象进行反序列化以创建新对象并且在此博客中我将详细讨论序列化和反序列化。 我们将以下面的Employee类对象为例进行说明 // If we use Serializable interface, static and transient variables do not get serialize Employee class implements Serializable { // 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 the class structure of our class, and we will get InvalidClassException, // If we provide 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 final String firstName; // Serialization process do not invoke the constructor but it can assign values to final fields private transient String middleName; // transient variables will not be serialized, serialised object holds null private String lastName; private int age; private static String department; // static variables will not be serialized, serialised object holds null public Employee(String firstName, String middleName, String lastName, int age, String department) { this .firstName firstName; this .middleName middleName; 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 public String toString() { return String.format( Employee {firstName%s, middleName%s, lastName%s, age%s, department%s} , firstName, middleName, lastName, age, department); } // 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 } // Custom deserialization logic // This will allow us to have additional deserialization logic on top of the default one eg decrypting object after deserialization private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { System.out.println( Custom deserialization logic invoked. ); ois.defaultReadObject(); // Calling the default deserialization logic // Age validation is just an example but there might some scenario where we might need to write some custom deserialization logic validateAge(); } } 什么是序列化和反序列化 在Java中我们创建了几个对象这些对象会相应地存活和死亡并且当JVM死亡时每个对象肯定会死亡但是有时我们可能想在多个JVM之间重用一个对象或者可能希望通过网络将对象传输到另一台机器。 好吧 序列化允许我们将对象的状态转换为字节流然后可以将其保存到本地磁盘上的文件中或者通过网络发送到任何其他计算机。 反序列化使我们可以逆转该过程这意味着将序列化的字节流再次转换为对象。 简而言之对象序列化是将对象的状态保存到字节序列中的过程 反序列化是从这些字节中重建对象的过程。 通常完整的过程称为序列化但我认为最好将两者都分类为更清晰。 序列化过程与平台无关可以在一个平台上反序列化在一个平台上序列化的对象。 要将对象序列化和反序列化为文件我们需要调用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()如以下代码所示 public class SerializationExample { public static void main(String[] args) throws IOException, ClassNotFoundException { Employee empObj new Employee( Shanti , Prasad , Sharma , 25 , IT ); System.out.println( Object before serialization empObj.toString()); // Serialization serialize(empObj); // Deserialization Employee deserialisedEmpObj deserialize(); System.out.println( Object after deserialization deserialisedEmpObj.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(); } } } 只有实现Serializable的类才能被序列化 类似于序列化中Java克隆的Cloneable接口我们有一个标记接口Serializable其作用类似于JVM的标志。 直接或通过其父级实现Serializable接口的任何类都可以序列化而没有实现Serializable则不能进行序列化。 Java的默认序列化过程是完全递归的因此每当我们尝试序列化一个对象时序列化过程都会尝试用我们的类 static和transient字段除外序列化所有字段原始和引用。 当一个类实现Serializable接口时其所有子类也都可以序列化。 但是当一个对象引用另一个对象时这些对象必须分别实现Serializable接口。 如果我们的类甚至具有对非Serializable类的单个引用则JVM将抛出NotSerializableException 。 为什么Object不能实现Serializable 现在出现一个问题如果序列化是非常基本的功能并且任何不能实现Serializable类都不能Serializable化那么为什么Serializable不是由Object本身实现的呢通过这种方式我们所有的对象都可以默认序列化。 Object类未实现Serializable接口因为我们可能不想序列化所有对象例如对线程进行序列化没有任何意义因为在JVM中运行的线程将使用系统的内存并将其持久化并尝试在JVM中运行毫无意义。 瞬态和静态字段不会序列化 如果我们要序列化一个对象但不想序列化某些特定字段则可以将这些字段标记为 短暂的 。 所有静态字段都属于类而不是对象并且序列化过程会序列化对象因此无法序列化静态字段。 序列化并不关心字段的访问修饰符例如private 。 所有非瞬态和非静态字段都被视为对象持久状态的一部分并且可以序列化。 我们只能在构造函数中将值分配给final字段而序列化过程不会调用任何构造函数但仍可以将值分配给final字段。 什么是serialVersionUID为什么要声明它 假设我们有一个类并且已将其对象序列化为磁盘上的文件并且由于一些新要求我们在类中添加/删除了一个字段。 现在如果我们尝试反序列化已经序列化的对象我们将得到InvalidClassException 为什么 我们之所以得到它是因为默认情况下JVM将版本号与每个可序列化的类相关联以控制类的版本控制。 它用于验证序列化和反序列化的对象具有相同的属性从而与反序列化兼容。 版本号保存在一个名为serialVersionUID的字段中。 如果可序列化的类未声明 serialVersionUID JVM将在运行时自动生成一个。 如果我们更改类结构例如删除/添加字段则版本号也会更改并且根据JVM我们的类与序列化对象的类版本不兼容。 这就是为什么我们会得到例外但是如果您真的考虑过它为什么应该仅仅因为我添加了一个字段就抛出该例外 不能仅将字段设置为其默认值然后下次将其写出吗 是的可以通过手动提供serialVersionUID字段并确保始终相同来完成此操作。 强烈建议每个可序列化的类声明其serialVersionUID因为生成的类是编译器相关的因此可能导致意外的InvalidClassExceptions。 您可以使用JDK发行版随附的实用程序称为 serialver以查看默认情况下该代码是什么默认情况下只是对象的哈希码。 使用writeObject和readObject方法自定义序列化和反序列化 JVM可以完全控制默认序列化过程中的对象序列化但是使用默认序列化过程有很多缺点其中包括 它无法处理无法序列化的字段的序列化。 反序列化过程在创建对象时不会调用构造函数因此它无法调用构造函数提供的初始化逻辑。 但是我们可以在Java类中覆盖此默认的序列化行为并提供一些其他逻辑来增强正常过程。 这可以通过在我们要序列化的类中提供两个方法writeObject和readObject来完成 // Custom serialization logic 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 { // Any Custom logic oos.defaultWriteObject(); // Calling the default serialization logic // Any Custom logic } // Custom deserialization logic will allow us to have additional deserialization logic on top of the default one eg decrypting object after deserialization private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { // Any Custom logic ois.defaultReadObject(); // Calling the default deserialization logic // Any Custom logic } 将两个方法都声明为私有是必要的公共方法将不起作用因此除了JVM外其他任何东西都看不到它们。 这也证明方法既不被继承也不被覆盖或重载。 JVM自动检查这些方法并在序列化/反序列化过程中调用它们。 JVM可以调用这些私有方法但其他对象则不能因此类的完整性得以维护序列化协议可以继续正常工作。 即使提供了那些专用的私有方法通过调用ObjectOutputStream.writeObject()或ObjectInputStream.readObject() 对象序列化的工作方式也相同。 对ObjectOutputStream.writeObject()或ObjectInputStream.readObject()的调用将启动序列化协议。 首先检查对象以确保其实现了Serializable 然后检查该对象是否提供了这些私有方法中的任何一个。 如果提供了它们则将流类作为参数传递给这些方法从而使代码可以控制其用法。 我们可以调用ObjectOutputStream.defaultWriteObject()和 这些方法中的ObjectInputStream.defaultReadObject()获得默认的序列化逻辑。 这些调用听起来很像-他们执行序列化对象的默认写入和读取操作这很重要因为我们没有替换正常的过程而只是添加了它。 这些私有方法可用于您要在序列化过程中进行的任何自定义例如可以将加密添加到输出中并将解密添加到输入中请注意字节以明文形式写入和读取完全没有混淆。 它们可能被用来向流中添加额外的数据也许是公司的版本代码其可能性实际上是无限的。 停止序列化和反序列化 假设我们有一个从其父级获得序列化功能的类这意味着我们的类是从另一个实现Serializable类扩展的。 这意味着任何人都可以序列化和反序列化我们类的对象。 但是如果我们不希望对类进行序列化或反序列化例如我们的类是单例并且希望防止任何新对象的创建记住反序列化过程会创建一个新对象 。 要停止类的序列化我们可以再次使用上述私有方法抛出NotSerializableException 。 现在任何对我们的对象进行序列化或反序列化的尝试都将始终导致引发异常。 并且由于这些方法被声明为private方法因此没有人可以覆盖您的方法并进行更改。 private void writeObject(ObjectOutputStream oos) throws IOException { throw new NotSerializableException( Serialization is not supported on this object! ); } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { throw new NotSerializableException( Serialization is not supported on this object! ); } 但是这违反了《里斯科夫换人原则》。 和 writeReplace和readResolve方法可用于实现类似行为的单例。 这些方法用于允许对象在ObjectStream中为其自身提供替代表示。 简单来说readResolve可用于更改通过readObject方法反序列化的数据而writeReplace可用于更改通过writeObject序列化的数据。 Java 序列化还可以用于深度克隆对象 。 Java克隆是Java社区中最有争议的话题它的确有其缺点但是在对象完全满足Java克隆的强制条件之前它仍然是创建对象副本的最流行和最简单的方法。 我在3篇文章的Java克隆系列中详细介绍了克隆 其中包括Java克隆和克隆类型浅和深等文章 并带有示例 Java克隆–复制构造器与克隆 Java克隆–甚至复制构造器都不是如果您想了解更多有关克隆的知识请充分阅读它们。 结论 序列化是将对象的状态保存为字节序列的过程然后可以将其存储在文件中或通过网络发送 反序列化是从这些字节中重建对象的过程。 只有Serializable接口的子类可以序列化。 如果我们的类未实现Serializable接口或者该类具有对非Serializable类的引用则JVM将抛出NotSerializableException 。 所有transient和static字段都不会序列化。 serialVersionUID用于验证序列化和反序列化的对象具有相同的属性从而与反序列化兼容。 我们应该在我们的类中创建一个serialVersionUID字段这样如果我们更改类结构添加/删除字段JVM将不会通过InvalidClassException 。 如果我们不提供它那么JVM提供的类可能会随着类结构的改变而改变。 通过提供writeObject和readObject方法的实现我们可以覆盖Java类内部的默认序列化行为。 我们可以从writeObject和readObject方法调用ObjectOutputStream.defaultWriteObject()和ObjectInputStream.defaultReadObject以获得默认的序列化和反序列化逻辑。 如果我们不希望类被序列化或反序列化则可以从writeObject和readObject抛出NotSerializableException异常。 可以使用“可Externalizable接口进一步定制和增强Java序列化过程我已经在“ 如何通过使用可外部化接口在Java中自定义序列化”中进行了解释。 我还写了一系列文章解释了有效Java的项目编号74到78它进一步讨论了如何增强Java序列化过程请继续阅读如果愿意的话。 您可以在此Github存储库中找到本文的完整源代码请随时提供宝贵的反馈。 翻译自: https://www.javacodegeeks.com/2019/08/serialization-everything-java-serialization-explained.html