Serializable
機能は果たしましたが、プロセス全体の自動実装の何が気に入らないのでしょうか? また、私たちが検討した例も単純なものでした。だから問題は何ですか?本質的に同じタスクに別のインターフェースが必要なのはなぜでしょうか? 実際のところ、それにはSerializable
いくつかの欠点があります。それらのいくつかをリストします。
-
パフォーマンス。この
Serializable
インターフェイスには多くの利点がありますが、高いパフォーマンスがその 1 つではないことは明らかです。まず、
Serializable
の内部実装により、大量のサービス情報とあらゆる種類の一時データが生成されます。2 番目に、
Serializable
Reflection API に依存します (今すぐこれについて深く調べる必要はありません。興味があれば、暇なときに詳細を読むことができます)。これにより、Java では一見不可能に見えること、たとえばプライベート フィールドの値を変更できるようになります。CodeGym にはReflection API に関する優れた記事があります。それについてはそこで読むことができます。 -
柔軟性。インターフェイスを使用する場合、シリアル化と逆シリアル化のプロセスは制御しません
Serializable
。一方で、これは非常に便利です。特にパフォーマンスを気にしないのであれば、コードを書く必要がないのは良いことのように思えます。しかし、実際に独自の機能 (以下に例を示します) をシリアル化ロジックに追加する必要がある場合はどうすればよいでしょうか?
基本的に、プロセスを制御する必要があるのは、
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;
// ...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 つのメソッドを実装しました。適切なタイミングで、必要なデータを暗号化または復号化し、暗号化されたデータを使用してオブジェクトをシリアル化します。これが実際にどのようになるかを見てみましょう。 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("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; 有効なコンストラクターがありませ 
Serializable
。Serializable
と の違いは、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
クラスにはデフォルトのコンストラクターも必要です。シリアル化メカニズムに関する優れた記事へのリンクは次のとおりです。
次回まで!:)
GO TO FULL VERSION