CodeGym /Java 博客 /随机的 /Java 中的可外部化接口
John Squirrels
第 41 级
San Francisco

Java 中的可外部化接口

已在 随机的 群组中发布
你好!今天我们继续了解Java对象的序列化和反序列化。在上一课中,我们了解了Serializable标记接口,回顾了它的使用示例,还学习了如何使用transient关键字来控制序列化过程。好吧,说我们“控制过程”可能言过其实了。我们有一个关键字,一个版本标识符,仅此而已。其余的过程隐藏在Java内部,我们无法访问它。当然,就方便性而言,这很好。但是程序员不应该只以自己的舒适为指导,对吧?:) 您还需要考虑其他因素。这就是为什么可序列化不是 Java 中序列化-反序列化的唯一机制。今天我们将熟悉Externalizable接口。但在我们开始研究它之前,您可能会有一个合理的问题:为什么我们需要另一种机制?Serializable完成了它的工作,为什么不喜欢整个过程的自动执行呢?我们看到的例子也不复杂。所以有什么问题?为什么我们需要另一个接口来完成本质上相同的任务?事实上,它Serializable有几个缺点。我们列出了其中的一些:
  1. 表现。接口Serializable有很多优点,但高性能显然不是其中之一。

    Externalizable 接口介绍 - 2

    首先, Serializable的内部实现会产生大量的服务信息和各种临时数据。

    其次, Serializable依赖于 Reflection API(您现在不必深入研究;如果您有兴趣,可以在闲暇时阅读更多内容)。这个东西可以让你做 Java 中看似不可能的事情:例如,改变私有字段的值。CodeGym 有一篇关于反射 API 的优秀文章。你可以在那里阅读它。

  2. 灵活性。我们在使用接口时不控制序列化-反序列化过程Serializable

    一方面,它非常方便,因为如果我们不是特别关心性能,那么不用编写代码似乎很好。但是,如果我们真的需要向序列化逻辑添加一些我们自己的功能(我们将在下面提供示例)怎么办?

    基本上,我们所要控制的过程就是transient排除一些数据的关键字。就是这样。那是我们的整个工具箱:/

  3. 安全。此项目部分源自上一个项目。

    我们之前没有花太多时间思考这个问题,但是如果你课堂上的某些信息不是为了让别人窥探,那该怎么办?一个简单的例子是密码或其他个人用户数据,在当今世界,这些数据受一系列法律管辖。

    如果我们使用Serializable,我们真的无能为力。我们按原样序列化所有内容。

    但如果我们以正确的方式进行,我们必须在将此类数据写入文件或通过网络发送之前对其进行加密。但Serializable不会使这成为可能。

Externalizable 接口介绍 - 3好吧,最后让我们看看如果我们使用Externalizable接口这个类会是什么样子。

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class UserInfo implements Externalizable {

   private String firstName;
   private String lastName;
   private String superSecretInformation;

private static final long SERIAL_VERSION_UID = 1L;

   // ...constructor, getters, setters, toString()...

   @Override
   public void writeExternal(ObjectOutput out) throws IOException {

   }

   @Override
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

   }
}
如您所见,我们发生了重大变化!主要的一个很明显:实现Externalizable接口时,必须实现两个必需的方法:writeExternal()readExternal(). 正如我们之前所说,序列化和反序列化的责任在于程序员。但是现在可以解决进程无法控制的问题了!整个过程由您直接编程。自然地,这允许更灵活的机制。此外,安全问题也得到解决。如您所见,我们班级有一个个人数据字段,不能未加密存储。现在我们可以轻松编写满足此约束的代码。例如,我们可以在我们的类中添加两个简单的私有方法来加密和解密敏感数据。我们将数据写入文件并以加密形式从文件中读取。其余数据将按原样写入和读取 :) 结果,我们的类看起来像这样:

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Base64;

public class UserInfo implements Externalizable {

   private String firstName;
   private String lastName;
   private String superSecretInformation;

   private static final long serialVersionUID = 1L;

   public UserInfo() {
   }

   public UserInfo(String firstName, String lastName, String superSecretInformation) {
       this.firstName = firstName;
       this.lastName = lastName;
       this.superSecretInformation = superSecretInformation;
   }

   @Override
   public void writeExternal(ObjectOutput out) throws IOException {
       out.writeObject(this.getFirstName());
       out.writeObject(this.getLastName());
       out.writeObject(this.encryptString(this.getSuperSecretInformation()));
   }

   @Override
   public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
       firstName = (String) in.readObject();
       lastName = (String) in.readObject();
       superSecretInformation = this.decryptString((String) in.readObject());
   }

   private String encryptString(String data) {
       String encryptedData = Base64.getEncoder().encodeToString(data.getBytes());
       System.out.println(encryptedData);
       return encryptedData;
   }

   private String decryptString(String data) {
       String decrypted = new String(Base64.getDecoder().decode(data));
       System.out.println(decrypted);
       return decrypted;
   }

   public String getFirstName() {
       return firstName;
   }

   public String getLastName() {
       return lastName;
   }

   public String getSuperSecretInformation() {
       return superSecretInformation;
   }
}
我们实现了两种方法,它们使用我们在关于 的课程中​​已经遇到的 相同参数ObjectOutput和参数。在适当的时候,我们加密或解密所需的数据,并使用加密的数据序列化我们的对象。让我们看看这在实践中是怎样的: ObjectInputSerializable

import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileOutputStream fileOutputStream = new FileOutputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);

       UserInfo userInfo = new UserInfo("Paul", "Piper", "Paul Piper's passport data");

       objectOutputStream.writeObject(userInfo);

       objectOutputStream.close();

   }
}
encryptString()decryptString()方法中,我们专门添加了控制台输出以验证写入和读取秘密数据的形式。上面的代码显示如下一行: SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh 加密成功!文件的完整内容如下所示: ¬н sr UserInfoÓ!}͐џC‚ћ xpt Ivant Ivanovt $SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRhx 现在让我们尝试使用我们的反序列化逻辑。

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);


       UserInfo userInfo = (UserInfo) objectInputStream.readObject();
       System.out.println(userInfo);

       objectInputStream.close();

   }
}
好吧,这里似乎没有什么复杂的。它应该工作! 我们运行它并得到... Exception in thread "main" java.io.InvalidClassException: UserInfo; 没有有效的构造 Externalizable 接口介绍 - 4函数 :( 显然,这不是那么容易!反序列化机制抛出一个异常并要求我们创建一个默认构造函数。我想知道为什么。有了 ,Serializable我们没有一个……:/ 这里我们遇到了另一个重要的细微差别。Serializable和 的区别Externalizable不仅在于程序员“扩展”的访问权限和更灵活地控制进程的能力,还在于进程本身。最重要的是反序列化机制。当使用Serializable,简单地为对象分配内存,然后从流中读取值并用于设置对象的字段。如果我们使用Serializable,则不会调用对象的构造函数!所有的工作都是通过反射(反射 API,我们在上节课中简要提到过)进行的。有了Externalizable,反序列化机制就不同了。默认构造函数首先被调用。只有在那之后才会调用创建的UserInfo对象的readExternal()方法。它负责设置对象的字段。这就是为什么任何实现该Externalizable接口的类都必须有默认构造函数的原因。让我们在我们的UserInfo类中添加一个并重新运行代码:

import java.io.*;

public class Main {

   public static void main(String[] args) throws IOException, ClassNotFoundException {

       FileInputStream fileInputStream = new FileInputStream("C:\\Users\\Username\\Desktop\\save.ser");
       ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);


       UserInfo userInfo = (UserInfo) objectInputStream.readObject();
       System.out.println(userInfo);

       objectInputStream.close();
   }
}
控制台输出: Paul Piper 的护照数据 UserInfo \ firstName = 'Paul', lastName = 'Piper', superSecretInformation = 'Paul Piper's passport data' } 现在完全不同了!首先,解密后的带有秘密信息的字符串显示在控制台上。然后我们从文件中恢复的对象显示为字符串!所以我们已经成功解决了所有问题:) 序列化和反序列化的主题看似简单,但是,正如您所看到的,课程很长。 还有更多我们没有涵盖的内容!使用这些接口中的每一个时,仍然涉及许多微妙之处。但是为了避免过多的新信息让您的大脑爆炸,我将简要列出一些更重要的要点,并为您提供额外阅读的链接。那么,您还需要了解什么? 首先,在序列化期间(无论您使用的是Serializable还是Externalizable),请注意 static变量。当您使用 时Serializable,这些字段根本不会序列化(因此,它们的值不会改变,因为static字段属于类,而不属于对象)。但是当你使用Externalizable,你自己控制这个过程,所以从技术上讲你可以序列化它们。但是,我们不推荐这样做,因为这样做可能会产生很多细微的错误。 其次,还应注意带有final修饰符的变量。当你使用的时候Serializable,它们像往常一样被序列化和反序列化,但是当你使用的时候,反序列化一个变量Externalizable是不可能的final!原因很简单:final当调用默认构造函数时,所有字段都会被初始化——之后,它们的值将无法更改。因此,要序列化具有final字段的对象,请使用Serializable. 第三,当你使用继承时,所有继承一些的后代类Externalizable类还必须具有默认构造函数。这是有关序列化机制的好文章的链接: 直到下一次!:)
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION