想象一下,你有一个静态的无参数方法,它是幂等的并且总是返回相同的值,并且可能会抛出一个检查异常,如下所示:
class Foo {
public static Pi bar() throws Baz { getPi(); } // gets Pi, may throw
}
现在,如果构造返回的 Object 的东西很昂贵并且永远不会改变,那么这对于惰性单例来说是一个很好的候选者。一种选择是 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 仍然是反模式吗?我在这里是否缺少其他解决方案(也可以使用诸如 racy single check 之类的小变体,但不要从根本上改变解决方案)?有充分的理由选择其中一个吗?
我没有尝试上面的示例,所以它们完全有可能无法编译。
编辑: 我没有重新实现或重新构建这个单例的消费者(即调用者Foo.bar()
)的奢侈,也没有机会引入一个 DI 框架来解决这个问题。我最感兴趣的是在给定约束内解决问题的答案(提供带有已检查异常的单例传播给调用者)。
更新:毕竟我决定使用 DCL,因为它提供了保存现有合同的最干净的方式,并且没有人提供应该避免正确执行 DCL 的具体原因。我没有在接受的答案中使用该方法,因为它似乎只是实现同一目标的一种过于复杂的方法。