Serializable
私は自分の仕事に対処しました、そしてプロセス全体の自動実装は喜ばざるを得ません。私たちが検討した例も複雑ではありませんでした。それで、どういうことですか?本質的に同じタスクに別のインターフェイスを使用する理由は何でしょうか? Serializable
多くのデメリットがあるのも事実です。それらのいくつかをリストしてみましょう:
-
パフォーマンス。このインターフェイスには
Serializable
多くの利点がありますが、高いパフォーマンスがその 1 つではないことは明らかです。
まず、内部メカニズムにより、Serializable
運用中に大量のサービス情報とさまざまな種類の一時データが生成されます。
2 番目に(ここで説明する必要はありません。興味があればいつでも読んでください)、この作業はSerializable
Reflection API の使用に基づいています。この仕組みを使用すると、Java では不可能と思われることを行うことができます。たとえば、プライベート フィールドの値を変更するなどです。JavaRush にはReflection API に関する優れた記事があり、ここで読むことができます。
-
柔軟性。を使用する場合、シリアル化と逆シリアル化のプロセスはまったく制御されません
Serializable
。一方で、これは非常に便利です。なぜなら、パフォーマンスをあまり気にしないのであれば、コードを書かなくても済む機能は便利に思えるからです。しかし、実際に独自の機能のいくつか (そのうちの 1 つの例を以下に示します) をシリアル化ロジックに追加する必要がある場合はどうすればよいでしょうか?
基本的に、プロセスを制御する必要があるのは
transient
、一部のデータを除外するキーワードだけです。それだけです。「ツールキット」のようなもの:/ -
安全性。この点は部分的に前の点から引き継がれています。
これまであまり考えたことはありませんでしたが、クラス内の一部の情報が「他の人の耳」 (より正確には目) を対象としていない場合はどうなるでしょうか? 簡単な例としては、パスワードやその他のユーザーの個人データが挙げられますが、現代世界ではこれらは多くの法律によって規制されています。
を使用しても
Serializable
、実際には何もできません。すべてをそのままシリアル化します。ただし、良い意味で、この種のデータは、ファイルに書き込んだりネットワーク経由で送信したりする前に暗号化する必要があります。しかし、
Serializable
それはこの機会を与えません。
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 つのメソッドを実装しました。適切なタイミングで、必要なデータを暗号化または復号化し、この形式でそれを使用してオブジェクトをシリアル化します。これが実際にどのようになるかを見てみましょう。 ObjectInput
Serializable
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; 有効なコンストラクターがあり ません おっと:( それはそれほど単純ではないことが判明しました! 逆シリアル化メカニズムが例外をスローし、デフォルトのコンストラクターを作成する必要がありました。なぜでしょうか?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
クラスから派生するすべての継承クラスにもデフォルトのコンストラクターが必要です。シリアル化メカニズムに関する優れた記事へのリンクをいくつか示します。
またね!:)
GO TO FULL VERSION