2010年5月29日土曜日

Effective javaまとめ6 ファイナライザを避ける3

ipad発売。
ほしいなぁ・・。

-----------------------------------------------------
Effective javaまとめ6 ファイナライザを避ける3

ファイナイライザの正当な使い方は何かあるか、というとこまで話しました。
正当な使い方は以下の通りです。

■安全ネットとして使う
前回説明した明示的終了メソッドの呼び出しを、オブジェクトの所有者が忘れた場合の「安全ネット」として振舞うことです。

ファイナイライザが即座に実行される保証が無いとはいえ、クライアントが明示的終了メソッドを呼び出すことによる契約の終わりを守りことに失敗した場合に、重要な資源を決して解放しないより、後で解放する方が良いです。

身近な明示的終了メソッドの例として、InputStream, OutputStreamクラスではcloseメソッドが提供されていますが、これらのクラスは、安全ネットとしてのファイナライザを保持しています。
(だからといって、明示的終了メソッドをクライアントが呼び出さないで良いわけではないです。なんども記載していますが、ファイナライザが呼び出されるタイミングは不定であるため、パフォーマンスに悪影響を及ぼすと予想されます)

FileOutputStreamのファイナライザはこんなかんじです。
------------------------------------------------
// ファイルをオープンしているとオブジェクトがはいっているぽいです
private FileDescriptor fd;

protected void finalize() throws IOException {
if (fd != null) {
if (fd == fd.out || fd == fd.err) {
flush();
else {
close();
}
}
------------------------------------------------

■ネイティブピアを回収する。
ネイティブピアとは、通常のオブジェクトがネイティブメソッドを通して委譲を行うネイティブオブジェクトです。
ネイティブピアは通常のオブジェクトではないので、ガーベージコレクタはネイティブピアについて知りませんし、通常のオブジェクトが回収される時に、ネイティブピアを回収することもできません。
ネイティブピアが重要な資源を保持していないと仮定すると、ファイナライザはネイティブピアを回収する処理を行うのに適切な手段です。


さて、ファイナライザに関して注意すべき点を記します。

「ファイナライザ連鎖」は自動的に実行されません。
Object以外のクラスがファイナライザを持ち、サブクラスがそれをオーバーライドしているならば、そのサブクラスのファイナライザはスーパークラスのファイナライザを手作業で呼び出さなければなりません。
tryブロック内でファイナライザを呼び出し、finallyブロック内でスーパークラスのファイナライザを呼び出すべきです。それにより、確実にサブクラスのファイナライザを呼び出すことができます。

サブクラスの実装者がスーパークラスのファイナライザを呼び出すのを忘れた場合、もしくは悪意を持って呼び出すことを行わなかった場合、スーパークラスのファイナライザは絶対に呼び出されません。

上記のような事象を防御するための方法として、ファイナライズを行う必要があるクラスにファイナライザを書く代わりに、エンクロージングインスタンス(内部クラスにとっての外部クラスのインスタンス)をファイナライズする事だけを目的とした無名クラスにファイナライザを書く、という方法が挙げられます。

その無名クラスの1つのインスタンスは、ファイナライザガーディアン(カコイイ!)と呼ばれ、エンクロージングクラスの個々のインスタンスに対して生成されます。

エンクロージングインスタンスは、privateのインスタンスフィールドにそのファイナライザガーディアンへの唯一の参照を保持するようにします。それにより、ファイナライザガーディアンはエンクロージングインスタンスと同時にファイナライズ対象となります。

ガーディアンがファイナライズされる時に、ガーディアンのファイナライザが、エンクロージングクラスに対するメソッドであるかのように、ガーディアンはエンクロージングインスタンスに必要なファイナライズ処理を行います。

ファイナライザガーディアンのイメージ
---------------------------------------------
public class IamAHero {
 private final Object finalizeGuardian = new Object() {
  protected void finalize() throws Throwable {
   // IamAHeroをファイナライズする
  }
 }
}
---------------------------------------------

この技法は、ファイナライザを持つ全てのfinalではないpublicのクラスに対して検討すべきです。

■まとめ
安全ネット、あるいはネイティブピアオブジェクトを解放する以外の目的でファイナライザを使用するべきではありません。
使用するときは、スーパークラスのファイナライザを呼び出すことを忘れてはいけません。
publicでfinalではないクラスにファイナライザを提供する必要がある場合、ファイナライザガーディアンの使用を検討してください。

----> おわり

0 件のコメント:

コメントを投稿