2

次のように、べき等で常に同じ値を返し、チェック例外をスローする可能性のある引数のない静的メソッドがあるとします。

class Foo {
 public static Pi bar() throws Baz { getPi(); } // gets Pi, may throw 
}

返されたオブジェクトを構築するものが高価で決して変更されない場合、これは遅延シングルトンの良い候補です。1 つの選択肢は、Holder パターンです。

class Foo {
  static class PiHolder {
   static final Pi PI_SINGLETON = getPi();
  }
  public static Pi bar() { return PiHolder.PI_SINGLETON; }
}

残念ながら、(暗黙の) 静的初期化ブロックからはチェック済み例外をスローできないため、これはうまくいきません。コールbar()):

class Foo {
  static class PiHolder {
   static final Pi PI_SINGLETON;
   static { 
    try { 
     PI_SINGLETON =  = getPi(); }
    } catch (Baz b) {
     throw new ExceptionInInitializerError(b);
    }
  }

  public static Pi bar() throws Bar {
   try {
    return PiHolder.PI_SINGLETON;
   } catch (ExceptionInInitializerError e) {
    if (e.getCause() instanceof Bar)
     throw (Bar)e.getCause();
    throw e;
   }
}

この時点で、ロックを再確認したほうがすっきりするのではないでしょうか?

class Foo {
 static volatile Pi PI_INSTANCE;
 public static Pi bar() throws Bar {
  Pi p = PI_INSTANCE;
  if (p == null) {
   synchronized (this) {
    if ((p = PI_INSTANCE) == null)
     return PI_INSTANCE = getPi();
   }
  }
  return p;
 }
}

DCL はまだアンチパターンですか? ここで見逃している他の解決策はありますか (際どいシングル チェックのようなマイナー バリアントも可能ですが、解決策を根本的に変更しないでください)。どちらかを選択する正当な理由はありますか?

上記の例は試していないので、コンパイルできない可能性は十分にあります。

編集: このシングルトンの消費者 (つまり、の呼び出し元) を再実装または再構築する余裕はありFoo.bar()ません。また、この問題を解決するために DI フレームワークを導入する機会もありません。私は主に、特定の制約内で問題を解決する回答 (呼び出し元に伝達されるチェック済み例外を含むシングルトンの提供) に興味があります。

更新:結局、DCL を使用することにしました。これは、既存のコントラクトを保持する最もクリーンな方法を提供し、適切に行われた DCL を避けるべき具体的な理由を誰も提供しなかったためです。同じことを達成するための非常に複雑な方法であるように思われたため、受け入れられた回答ではこの方法を使用しませんでした。

4

4 に答える 4

1

これが何のために必要なのかを教えてくれないので、それを達成するためのより良い方法を提案するのはちょっと難しい. 怠惰なシングルトンが最善のアプローチになることはめったにないと言えます。

ただし、コードにはいくつかの問題があります。

try {
    return PiHolder.PI_SINGLETON;
} catch (ExceptionInInitializerError e) {

フィールド アクセスが例外をスローすることをどのように期待しますか?


編集: Irreputable が指摘しているように、アクセスによってクラスの初期化が発生し、静的初期化子が例外をスローしたために初期化が失敗した場合、実際にはここで ExceptionInInitializerError を取得します。ただし、次のコードが示すように、VM は最初の失敗後にクラスの初期化を再試行せず、別の例外でこれを伝えます。

static class H {
    final static String s; 
    static {
        Object o = null;
        s = o.toString();
    }
}

public static void main(String[] args) throws Exception {
    try {
        System.out.println(H.s);
    } catch (ExceptionInInitializerError e) {
    }
    System.out.println(H.s);
}

結果:

Exception in thread "main" java.lang.NoClassDefFoundError: Could not initialize class tools.Test$H 
        at tools.Test.main(Test.java:21)

ExceptionInInitializerError ではありません。


二重チェックのロックにも同様の問題があります。構築が失敗した場合、フィールドは null のままになり、PIPI がアクセスされるたびに を構築するための新しい試行が行われます。失敗が永続的で費用がかかる場合は、別の方法を使用することをお勧めします。

于 2011-03-02T21:15:58.993 に答える
1

一般的に、Singleton と変更可能な静的変数を捨てることを強くお勧めします。「コンストラクタを正しく使用してください。」オブジェクトを構築し、それを必要とするオブジェクトに渡します。

于 2011-03-02T20:58:42.963 に答える
1

私の経験では、取得しようとしているオブジェクトが単純なコンストラクター呼び出し以上のものを必要とする場合は、依存性注入を使用するのが最善です。

public class Foo {
  private Pi pi;
  public Foo(Pi pi) {
    this.pi = pi;
  }
  public Pi bar() { return pi; }
}

...または怠惰が重要な場合:

public class Foo {
  private IocWrapper iocWrapper;
  public Foo(IocWrapper iocWrapper) {
    this.iocWrapper = iocWrapper;
  }
  public Pi bar() { return iocWrapper.get(Pi.class); }
}

(詳細は、DI フレームワークによって多少異なります)

オブジェクトをシングルトンとしてバインドするように DI フレームワークに指示できます。これにより、長期的にはより多くの柔軟性が得られ、クラスがより単体テストしやすくなります。

また、Java でのダブルチェック ロックは、JIT コンパイラによる命令の並べ替えの可能性があるため、スレッド セーフではないことを理解しています。 編集:メリットンが指摘するように、ダブルチェック ロックは Java で機能しますが、volatile キーワードを使用する必要があります。

最後のポイント: 適切なパターンを使用している場合、通常、クラスを遅延インスタンス化する理由はほとんどまたはまったくありません。コンストラクターを非常に軽量にし、ロジックの大部分をメソッドの一部として実行することをお勧めします。この特定のケースで必ずしも何か間違ったことをしていると言っているわけではありませんが、このシングルトンをどのように使用しているかを広く見て、物事を構造化するためのより良い方法がないかどうかを確認することをお勧めします.

于 2011-03-02T21:00:27.307 に答える
1

「ホルダー」トリックは、本質的に JVM によって実行されるダブル チェック ロックです。仕様によると、クラスの初期化は (二重チェック) ロックされています。JVM は DCL を安全に (そして高速に) 実行できますが、残念ながら Java プログラマーはその能力を利用できません。私たちができる最も近い方法は、中間の最終参照を使用することです。DCL のウィキペディアを参照してください。

例外を保持するという要件は難しくありません。

class Foo {
  static class PiHolder {
    static final Pi PI_SINGLETON;
    static Bar exception;
    static { 
      try { 
        PI_SINGLETON =  = getPi(); }
      } catch (Bar b) {
        exception = b;
      }
    }
  }
public Pi bar() throws Bar {
  if(PiHolder.exception!=null)
    throw PiHolder.exception;  
  else
    return PiHolder.PI_SINGLETON;
}
于 2011-03-02T21:01:07.890 に答える