JavaRush /Java Blog /Random-JA /Java の外部化可能なインターフェイス

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

Random-JA グループに公開済み
こんにちは!今日は、Java オブジェクトのシリアル化と逆シリアル化についての紹介を続けます。前回の講義では、 Serializableマーカー インターフェイスについて紹介し、その使用例を見て、transientキーワードを使用してシリアル化プロセスを制御する方法についても学びました。もちろん、「プロセスを管理する」というのは強い言葉です。キーワードが 1 つ、バージョン ID が 1 つあり、基本的にはそれだけです。プロセスの残りの部分は Java 内に「組み込まれ」ており、それにアクセスすることはできません。利便性の観点からは、これはもちろん良いことです。しかし、プログラマーは仕事において、自分の快適さだけを重視すべきではありませんよね? :) 他にも考慮すべき要素があります。したがって、Serializable はJava でのシリアル化と逆シリアル化のための唯一のツールではありません。今日は、 Externalizableインターフェイスについて説明します。しかし、研究に移る前から、「なぜ別のツールが必要なのでしょうか?」という当然の疑問が生じるかもしれません。Serializable私は自分の仕事に対処しました、そしてプロセス全体の自動実装は喜ばざるを得ません。私たちが検討した例も複雑ではありませんでした。それで、どういうことですか?本質的に同じタスクに別のインターフェイスを使用する理由は何でしょうか? Serializable多くのデメリットがあるのも事実です。それらのいくつかをリストしてみましょう:
  1. パフォーマンス。このインターフェイスにはSerializable多くの利点がありますが、高いパフォーマンスがその 1 つではないことは明らかです。

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

まず、内部メカニズムにより、Serializable運用中に大量のサービス情報とさまざまな種類の一時データが生成されます。
2 番目に(ここで説明する必要はありません。興味があればいつでも読んでください)、この作業はSerializableReflection API の使用に基づいています。この仕組みを使用すると、Java では不可能と思われることを行うことができます。たとえば、プライベート フィールドの値を変更するなどです。JavaRush にはReflection API に関する優れた記事があり、ここで読むことができます。

  1. 柔軟性。を使用する場合、シリアル化と逆シリアル化のプロセスはまったく制御されませんSerializable

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

    基本的に、プロセスを制御する必要があるのは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という 2 つの必須メソッドを実装する必要があります。前に述べたように、シリアル化と逆シリアル化に対するすべての責任はプログラマにあります。ただし、このプロセスを制御できないという問題は解決できるようになりました。プロセス全体はユーザーによって直接プログラムされるため、当然、より柔軟なメカニズムが作成されます。さらに、セキュリティの問題も解決されます。ご覧のとおり、クラスには、暗号化せずに保存できない個人データというフィールドがあります。これで、この制約を満たすコードを簡単に作成できるようになりました。たとえば、機密データを暗号化および復号化するための 2 つの単純なプライベート メソッドをクラスに追加します。それらをファイルに書き込み、暗号化された形式でファイルから読み取ります。そして、残りのデータをそのまま読み書きします:) その結果、クラスは次のようになります。 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とをパラメータとして 使用する 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("Ivan", "Ivanov", "Ivan Ivanov's passport data");

       objectOutputStream.writeObject(userInfo);

       objectOutputStream.close();

   }
}
encryptString()およびメソッド ではdecryptString()、シークレット データがどのような形式で読み書きされるかを確認するために、コンソールへの出力を具体的に追加しました。 上記のコードは、次の行をコンソールに出力します。 SXZhbiBJdmFub3YncyBwYXNzcG9ydCBkYXRh 暗号化に成功しました。 ファイルの完全な内容は次のようになります: Camt Ivant Ivant $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ません おっと:( それはそれほど単純ではないことが判明しました! 逆シリアル化メカニズムが例外をスローし、デフォルトのコンストラクターを作成する必要がありました。なぜでしょうか?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();
   }
}
コンソール出力: Ivan Ivanov のパスポート データ UserInfo{firstName='Ivan', lastName='Ivanov', superSecretInformation='Ivan Ivanov のパスポート データ'} まったく別の問題です。まず、秘密データを含む復号化された文字列がコンソールに出力され、次にオブジェクトが文字列形式でファイルから復元されました。これですべての問題を無事に解決できました :) シリアル化と逆シリアル化のトピックは単純なように見えますが、ご覧のとおり、私たちの講義は長くなりました。それだけではありません!これらの各インターフェイスを使用する際には、さらに多くの微妙な点がありますが、膨大な量の新しい情報で頭が混乱しないように、さらに重要な点をいくつか簡単にリストし、追加の資料へのリンクを提供します。では、他に何を知る必要があるでしょうか? まずSerializable、シリアル化するとき (または を使用するかは関係ありませんExternalizable)、変数に注意してくださいstatic。これらのフィールドは、使用時にSerializableまったくシリアル化されません (したがって、staticフィールドはオブジェクトではなくクラスに属しているため、値は変わりません)。しかし、それを使用するときは、Externalizableプロセスを自分で制御するため、技術的にはこれを行うことができます。ただし、これには微妙なエラーが含まれるため、お勧めしません。 次に、修飾子を含む変数にも注意を払う必要がありますfinal。使用すると、Serializable通常どおりシリアル化および逆シリアル化されますが、使用すると、変数Externalizableを逆シリアル化するfinalことはできません。理由は簡単です。finalデフォルトのコンストラクターが呼び出されたときにすべてのフィールドが初期化され、その後はその値を変更できなくなります。したがって、final-field を含むオブジェクトをシリアル化するには、 による標準のシリアル化を使用しますSerializable第三に、継承を使用する場合、あるExternalizableクラスから派生するすべての継承クラスにもデフォルトのコンストラクターが必要です。シリアル化メカニズムに関する優れた記事へのリンクをいくつか示します。 またね!:)
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION