CodeGym /Java Blog /ランダム /Java の外部化可能なインターフェイス
John Squirrels
レベル 41
San Francisco

Java の外部化可能なインターフェイス

ランダム グループに公開済み
やあ!今日は引き続き、 Java オブジェクトのシリアル化と逆シリアル化について学びます。前回のレッスンでは、 Serializableマーカー インターフェイスについて理解し、その使用例を確認し、transientキーワードを使用してシリアル化プロセスを制御する方法についても学びました。そうですね、私たちが「プロセスをコントロールする」というのは言い過ぎかもしれません。キーワードが 1 つ、バージョン識別子が 1 つあり、それだけです。プロセスの残りの部分は Java 内に隠されているため、アクセスできません。もちろん、利便性という点では、これは良いことです。しかし、プログラマーは自分の快適さだけに導かれるべきではありませんよね? :) 考慮する必要がある要素は他にもあります。だからこそシリアライズ可能Java のシリアル化と逆シリアル化の唯一のメカニズムではありません。今日は、 Externalizableインターフェイスについて説明します。しかし、それを研究し始める前に、「なぜ別のメカニズムが必要なのでしょうか?」という当然の疑問が生じるかもしれません。Serializable機能は果たしましたが、プロセス全体の自動実装の何が気に入らないのでしょうか? また、私たちが検討した例も単純なものでした。だから問題は何ですか?本質的に同じタスクに別のインターフェースが必要なのはなぜでしょうか? 実際のところ、それにはSerializableいくつかの欠点があります。それらのいくつかをリストします。
  1. パフォーマンス。このSerializableインターフェイスには多くの利点がありますが、高いパフォーマンスがその 1 つではないことは明らかです。

    外部化可能なインターフェースの紹介 - 2

    まず、 Serializableの内部実装により、大量のサービス情報とあらゆる種類の一時データが生成されます。

    2 番目に、 Serializable Reflection API に依存します (今すぐこれについて深く調べる必要はありません。興味があれば、暇なときに詳細を読むことができます)。これにより、Java では一見不可能に見えること、たとえばプライベート フィールドの値を変更できるようになります。CodeGym にはReflection API に関する優れた記事があります。それについてはそこで読むことができます。

  2. 柔軟性。インターフェイスを使用する場合、シリアル化と逆シリアル化のプロセスは制御しませんSerializable

    一方で、これは非常に便利です。特にパフォーマンスを気にしないのであれば、コードを書く必要がないのは良いことのように思えます。しかし、実際に独自の機能 (以下に例を示します) をシリアル化ロジックに追加する必要がある場合はどうすればよいでしょうか?

    基本的に、プロセスを制御する必要があるのは、transient一部のデータを除外するキーワードだけです。それでおしまい。これが私たちのツールボックス全体です :/

  3. 安全。この項目は、前の項目から一部派生しています。

    これまでこれについて考えることに多くの時間を費やしたことはありませんでしたが、クラス内の一部の情報が他の人の詮索好きな目や耳に向けられていない場合はどうなるでしょうか? 簡単な例としては、パスワードやその他のユーザーの個人データが挙げられますが、今日の世界ではこれらは多数の法律によって規制されています。

    を使用した場合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;

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

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

   }

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

   }
}
ご覧のとおり、大幅な変更が加えられています。主なものは明らかです。Externalizableインターフェイスを実装するときは、次の 2 つの必須メソッドを実装する必要がありますwriteExternal()readExternal()。前に述べたように、シリアル化と逆シリアル化の責任はプログラマにあります。しかし、プロセスを制御できないという問題を解決できるようになりました。プロセス全体は直接プログラムされます。当然のことながら、これにより、より柔軟なメカニズムが可能になります。さらに、セキュリティの問題も解決されます。ご覧のとおり、このクラスには、暗号化せずに保存できない個人データ フィールドがあります。これで、この制約を満たすコードを簡単に作成できるようになりました。たとえば、機密データを暗号化および復号化するための 2 つの単純なプライベート メソッドをクラスに追加できます。データをファイルに書き込み、暗号化された形式でファイルから読み取ります。残りのデータはそのまま書き込まれ、読み取られます:) その結果、クラスは次のようになります。

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と 同じパラメーターを使用する 2 つのメソッドを実装しました。適切なタイミングで、必要なデータを暗号化または復号化し、暗号化されたデータを使用してオブジェクトをシリアル化します。これが実際にどのようになるかを見てみましょう。 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();

   }
}
さて、ここでは何も複雑なことはないようです。うまくいくはずです! それを実行すると、 スレッド「メイン」で例外が発生します。java.io.InvalidClassException: UserInfo; 有効なコンストラクターがありませ 外部化可能なインターフェースの紹介 - 4ん :( どうやら、それほど簡単ではないようです! 逆シリアル化メカニズムが例外をスローし、デフォルトのコンストラクターを作成するように要求しました。なぜだろうか。 があれば、コンストラクターなしで済んだのですが... :/ ここで、別の重要なニュアンスに遭遇しましたSerializableSerializableと の違いは、Externalizableプログラマの「拡張された」アクセスとプロセスをより柔軟に制御する能力だけでなく、プロセス自体にもあり、とりわけ、逆シリアル化メカニズムにありますSerializableでは、メモリがオブジェクトに割り当てられるだけで、ストリームから値が読み取られ、オブジェクトのフィールドの設定に使用されます。を使用するとSerializable、オブジェクトのコンストラクターは呼び出されません。すべての作業はリフレクション (前回のレッスンで簡単に説明した Reflection API) を通じて行われます。ではExternalizable、逆シリアル化メカニズムが異なります。デフォルトのコンストラクターが最初に呼び出されます。その後初めて、作成されたUserInfoオブジェクトのreadExternal()メソッドが呼び出されます。オブジェクトのフィールドを設定する役割を果たします。このため、インターフェイスを実装するクラスにはExternalizableデフォルトのコンストラクターが必要です。クラスに 1 つ追加し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 のパスポート データ' } これはまったく 異なります。まず、復号化された秘密情報を含む文字列がコンソールに表示されました。すると、ファイルから復元したオブジェクトが文字列として表示されました。これで、すべての問題が正常に解決されました :) シリアル化と逆シリアル化のトピックは単純なように思えますが、ご覧のとおり、レッスンは長かったです。 そして、まだカバーしていないことがたくさんあります。これらの各インターフェイスを使用する場合には、依然として多くの微妙な点が伴います。ただし、過剰な新しい情報で頭が爆発するのを避けるために、さらに重要な点をいくつか簡単にリストし、追加の資料へのリンクを示します。それで、他に何を知る必要があるでしょうか? まず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