假设您有一个静态的无参数方法,该方法是幂等的,并且始终返回相同的值,并且可能会抛出一个已检查的异常,如下所示:
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仍然是反模式吗?我在这里还缺少其他解决方案吗(也可以使用诸如单次检查的次要变体,但是从根本上不改变解决方案)吗?是否有充分的理由选择一个?
我没有尝试上面的示例,因此很有可能它们没有被编译。
编辑:我没有重新实现或重新构造此单例的使用者(即
Foo.bar()
的调用者)的奢侈,也没有机会引入DI框架来解决此问题。我最感兴趣的是在给定的约束范围内解决问题的解决方案(提供一个已检查异常的单例,并传播到调用方)。更新:我决定最终选择DCL,因为它提供了保存现有合同的最干净的方法,而且没有人提供避免避免DCL正确执行的具体原因。我没有在公认的答案中使用该方法,因为这似乎是实现同一件事的一种过于复杂的方法。
最佳答案
本质上,“保持”技巧是由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;
}