JavaRush /Java 博客 /Random-ZH /Java 中的外部化接口

Java 中的外部化接口

已在 Random-ZH 群组中发布
你好!今天我们继续介绍Java对象的序列化和反序列化。在上一讲中,我们介绍了Serialized标记接口,查看了其使用示例,还学习了如何使用瞬态关键字控制序列化过程。嗯,“管理流程”当然是一个很强烈的词。我们只有一个关键字、一个版本 ID,基本上就是这样。该过程的其余部分是“硬连线”在 Java 内部的,无法访问它。从方便的角度来看,这当然是好的。但一个程序员在工作中不应该只关注自己的舒适度,对吧?:) 还有其他因素需要考虑。因此,Serialized并不是Java中唯一的序列化-反序列化工具。今天我们将熟悉Externalized接口。但即使在我们继续研究它之前,您可能会有一个合理的问题:为什么我们需要另一个工具?Serializable我应付了我的工作,整个流程的自动执行不能不让我庆幸。我们看到的例子也不复杂。那么到底是怎么回事呢?为什么要使用另一个界面来完成基本相同的任务?事实是Serializable它有很多缺点。让我们列出其中一些:
  1. 表现。该接口有Serializable很多优点,但高性能显然不是其中之一。

外部化接口简介 - 2

首先,内部机制Serializable在运行过程中会产生大量的服务信息和各类临时数据。
其次(如果您有兴趣,您现在不必深入讨论,可以在闲暇时阅读),这项工作Serializable基于使用 Reflection API。这个装置可以让你做一些在 Java 中看似不可能的事情:例如,更改私有字段的值。JavaRush 有一篇关于 Reflection API 的优秀文章,您可以在这里阅读。

  1. 灵活性。使用Serializable.

    一方面,这非常方便,因为如果我们并不真正关心性能,那么不编写代码的能力似乎很方便。但是,如果我们确实需要向序列化逻辑添加一些我们自己的功能(下面是其中之一的示例)怎么办?

    本质上,我们所需要控制的过程就是一个关键字transient来排除一些数据,仅此而已。有点像“工具包”:/

  2. 安全。这一点部分源于上一点。

    我们之前没有考虑过这个问题,但是如果你课堂上的某些信息不是为了“其他人的耳朵”(更准确地说,眼睛)而准备的怎么办?一个简单的例子是用户的密码或其他个人数据,在现代世界中受到一系列法律的监管。

    使用Serializable,我们实际上无能为力。我们按原样序列化一切。

    但是,以一种好的方式,我们必须在将此类数据写入文件或通过网络传输之前对其进行加密。但Serializable它不给这个机会。

外部化接口简介 - 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;

   //...конструктор, геттеры, сеттеры, 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 out和作为参数。在正确的时间,我们加密或解密必要的数据,并以这种形式使用它来序列化我们的对象。让我们看看这在实践中会是什么样子: 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("Ivan", "Ivanov", "Ivan Ivanov'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();

   }
}
嗯,这里看起来没什么复杂的,应该可以用! 让我们运行... 线程“main”中的异常 java.io.InvalidClassException: UserInfo; 没有有效的构造函数 外部化接口简介 - 4哎呀 :( 事实证明并不是那么简单!反序列化机制抛出了一个异常,并要求我们创建一个默认构造函数。我想知道为什么?Serializable我们没有它就成功了... :/ 这里我们遇到了另一个重要的细微差别Serializable和的区别Externalizable不仅在于程序员的“扩展”访问和更灵活管理进程的能力,还在于进程本身,首先在反序列化机制上……使用时,Serializable内存简单来说就是为一个对象分配,之后从流中读取值,填充它的所有字段。如果我们使用Serializable,则不会调用对象构造函数!所有工作都是通过反射(Reflection 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();
   }
}
控制台输出: Ivan Ivanov 的护照数据 UserInfo{firstName='Ivan', lastName='Ivanov', superSecretInformation='Ivan Ivanov 的护照数据'} 完全不同的事情!首先,解密后的带有秘密数据的字符串被输出到控制台,然后我们的对象以字符串格式从文件中恢复出来!这就是我们成功解决所有问题的方法:) 序列化和反序列化的主题看起来很简单,但正如你所看到的,我们的讲座结果很长。这还不是全部!使用这些界面时还有更多微妙之处,但为了让您的大脑不会因大量新信息而爆炸,我将简要列出一些更重要的要点并提供其他阅读材料的链接。那么您还需要了解什么? 首先,序列化时(无论使用Serializableor还是Externalizable),要注意变量static。使用时,Serializable这些字段根本不会序列化(因此,它们的值不会改变,因为static这些字段属于类,而不是对象)。但是在使用的时候,Externalizable你自己控制这个过程,所以从技术上来说这是可以做到的。但不建议这样做,因为这充满了微妙的错误。 其次,还应注意带有修饰符的变量final。使用时,Serializable它们像平常一样序列化和反序列化,但是使用时,不可能Externalizable反序列化final变量!原因很简单:所有final字段都会在调用默认构造函数时初始化,之后它们的值就无法更改。因此,要序列化包含final-fields 的对象,请通过Serializable. 第三,当使用继承时,从某个类派生的所有继承类Externalizable也必须具有默认构造函数。以下是一些有关序列化机制的好文章的链接: 再见!:)
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION