86:Serializableインタフェースを慎重に実現する


クラスのインスタンスをシーケンス化するには、その宣言に「implemants Serializable」という文字があれば簡単です.容易すぎるだけに,プログラム猿は苦労せずにシーケンス化できるという誤解が普遍的に存在する.実際の状況はずっと複雑だ.クラスをシーケンス化できる直接コストは無視できますが、長期的なコストは通常高いです.
Serializableインタフェースを実装するための最大の代価は、クラスがパブリッシュされると、「このクラスの実装を変更する」柔軟性が大幅に低下することです.クラスがSerializableインタフェースを実装するとそのバイトストリーム符号化(またはシーケンス化形式(serialized form))は、そのエクスポートされたAPIの一部となります.このクラスが広く使用されると、エクスポートされたAPIの他のすべての部分をサポートしなければならないように、このシーケンス化形式を永遠にサポートしなければならないことがよくあります.カスタムシーケンス化形式の設計に努力しない場合は、(custom serialized form)では、デフォルトのシーケンス化形式のみが受け入れられ、このシーケンス化形式はクラスの最初の内部表現に永遠に束縛されます.言い換えれば、デフォルトのシーケンス化形式を受け入れた場合、このクラスのプライベートおよびパケットレベルのプライベートのインスタンスドメインは、導出されたAPIの一部となり、「最小限のアクセスドメイン」に合致しません.の実践準則(15項)により、情報隠蔽ツールとしての有効性が失われる.
 デフォルトのシーケンス化形式を受け入れ、後でこのクラスの内部表現を変更すると、シーケンス化形式の互換性がなくなる可能性があります.クライアントプログラムはこのクラスの古いバージョンでクラスをシーケンス化しようとしています.その後、新しいバージョンで逆シーケンス化(逆も同様)を行うと、プログラムが失敗します.内部表現を変更しながらも元のシーケンス化形式を維持します.(ObjectOutputStream.putFieldsとObjectInputStream.readFieldsを使用)これも可能ですが、これは困難であり、ソースコードに明らかな危険性があります.クラスをシーケンス化する場合は、高品質のシーケンス化形式を注意深く設計し、長い間この形式を使用したいと思っています.(87、90項目).これにより開発の初期コストが増加するが、これは価値がある.丹念に設計されたシーケンス化形式でもクラスの進化が制限され、設計不良のシーケンス化形式が麻痺を招く可能性がある.
シーケンス化はクラスの進化を制限します.この制限の一例は、ストリームの一意識別子(stream unique identifier)に関係し、通常はシーケンスバージョンUIDとしても使用される.(serial version UID).シーケンス可能なクラスごとに一意の識別番号が関連付けられています.serialVersionUIDという静的finalというlongドメインを宣言してこの数値を指定すると、実行時にハッシュ関数を暗号化します.(SHA-1)クラスの構造に適用され、自動的に生成されます.この自動生成された値は、クラスの名前、実装されたいくつかのポート、およびほとんどのメンバーによって生成されます.(コンパイラによって生成された合成メンバーを含む)の影響です.たとえば、便利な方法を追加すると、生成されたシーケンスバージョンUIDが変化します.シーケンスバージョンUIDを宣言しないと互換性が損なわれ、実行時にInvalidClassException異常が発生します.
Serializableを実装する第2の代価は、BUGとセキュリティ・ホールが発生する可能性を増加させることです.通常、オブジェクトはコンストラクタを使用して作成されます.シーケンス化メカニズムは、言語以外のオブジェクト作成メカニズムです.デフォルトの動作を受け入れるか、デフォルトの動作を上書きするかにかかわらず、逆シーケンス化メカニズムは「非表示のコンストラクタ」です.、他のコンストラクタと同じ特徴を備えています.逆シーケンス化メカニズムには表示されないコンストラクタがあるため、逆シーケンス化プロセスは、コンストラクタによって確立されたすべての制約関係を保証し、コンストラクタ中のオブジェクトの内部情報に攻撃者がアクセスできないことを保証する必要があります.デフォルトの逆シーケンス化メカニズムにより、オブジェクトの制約関係が破壊されやすく、不正アクセスが発生します(88).
Serializableを実装する3番目の代価は、クラスが新しいバージョンを発行するにつれて、テストに関連する負担も増加することです.シーケンス可能なクラスが改訂された場合、重要なのは、「新しいバージョンでインスタンスをシーケンス化し、古いバージョンで逆シーケンス化できるかどうかを確認することです.」逆も同様です.したがって、テストに必要なワークロードは、「シーケンス可能なクラスの数と、発行可能なバージョン番号」の積に比例します.シーケンス化-逆シーケンス化プロセスが成功し、生成されたオブジェクトが元のオブジェクトのレプリカであることを確認する必要があります.最初にクラスを作成するときに、カスタムのシーケンス化形式を丹念に設計すれば、テストのニーズを低減することができます.
Serializableインタフェースを実装するには、簡単にできる決定ではありません.クラスがフレームワークに追加され、フレームワークがシーケンス化に依存してオブジェクトの転送または永続化を実現する場合、この点が重要です.さらに、Serializableを実装する必要がある別のクラスとしてクラスを使用するコンポーネントを大幅に簡略化します.しかし、Serializableを実装すると多くのオーバーヘッドが発生します.クラスを設計するたびに、コストと収益を比較します.経験によれば,BigIntegerやInstantのような値クラスがSerializableを実現し,集合もそうした.アクティブなエンティティを表すクラス、例えばスレッドプール(thread pool)は、Serializableを実装することは少ないはずです.
継承のために設計されたクラス(第19項)Serializableインタフェースを実装することは少なく、インタフェースもSerializableインタフェースを継承することは少ないはずである.この規則に違反すると、拡張クラスまたは実装インタフェースの誰にも負担がかかる.この規則に違反することが適切である場合がある.例えば、クラスまたはインタフェースの存在が低くない場合、主にあるフレームワークに参加するために、フレームワークはすべての参加者に要求されるSerializableインタフェースを実装する必要がある場合、このクラスまたはインタフェースにとって、Serializableインタフェースを実装または拡張することは非常に有意義である.
  継承のために設計されたクラスでは,Serializableインタフェースを実現したのはThrowableとComponentである.ThrowableはSerializableインタフェースを実現しているので、RMIはサーバからクライアントに異常を送信することができます.ComponentはSerializableインタフェースを実現しているので、GUIは送信、保存、復元できますが、今日のSwingやAWTでもこのツールは実際にはあまり使われていません.
 シーケンス可能および拡張可能なインスタンスフィールドを有するクラスを実装する場合、いくつかのリスクに注意する必要があります.インスタンスフィールド値に制約がある場合は、finalizeを書き換えてfinalと宣言することで、サブクラスがfinalizeメソッドを上書きしないことが重要です.それ以外の場合、クラスはターミネーター攻撃(finalizer attacks)(8番目)を受けやすくなります.最後に、クラスのインスタンスフィールドがデフォルト値(整数タイプがゼロ、ブール値がfalse、オブジェクト参照タイプがnull)に初期化されると、制約に違反します.readObjectNoDataメソッドを追加する必要があります.
// readObjectNoData for stateful extendable serializable classes
private void readObjectNoData() throws InvalidObjectException {
    throw new InvalidObjectException("Stream data required");
}

  Java 4にこの方法が追加され、既存のシーケンス化可能クラス[Serialization,3.5]にシーケンス化可能スーパークラスを追加する極端な状況をカバーする.
  Serializableを実現しない決定について注意が必要である.継承のために設計されたクラスがシーケンス化できない場合は、シーケンス化可能なサブクラスを記述するには追加の努力が必要になる場合があります.このクラスの通常の逆シーケンス化は、スーパークラスがアクセス可能な無パラメータ構造関数[Serialization,1.10]を有することを要求する.このようなコンストラクション関数を指定しない場合は、サブクラスにシーケンス化エージェントモード(90番目)を強制します.
内部クラス(24)ではSerializableインタフェースを実装するべきではありません.コンパイラによって生成された*合成ドメイン(synthetic field)を使用して、周辺インスタンス(enclosing instabce)*への参照を保存し、周辺の役割ドメインからのローカル変数の値を保存します.「これらのドメインがクラス定義にどのように対応するか」匿名クラスやローカルクラスの名前が指定されていないように明確な規定はありません.したがって、内部クラスのデフォルトのシーケンス化形式は定義が不明です.しかしながら、*静的メンバークラス(static member class)は、Serializableインタフェースを実装することができる.
要するに、Serializableインタフェースを実装するのは簡単に見えるだけです.保護された環境でのみクラスを使用しない限り、各バージョン間で相互運用を行う必要はなく、サーバが信頼されていないデータに露出することはありません.そうしないと、Serializableインタフェースを実現することは厳格な約束であり、真剣に対処する必要があります.クラスが継承を許可する場合は、特に注意する必要があります.