我正在尝试编写一个通用功能,可根据需要执行线程安全的可选延迟初始化。我不能使用标准模式,因为该值不是最终的,并且可以通过setter设置。
在Java 8中,我通过向供应商编写通用LazyInitializer
来解决这个问题:
public class LazyInitializer<T> {
protected final Supplier<T> initializer;
protected AtomicReference<T> value = new AtomicReference<>();
public LazyInitializer(final Supplier<T> initializer) {
this.initializer = initializer;
}
public T get() {
T result = this.value.get();
if (result == null) {
this.value.compareAndSet(null, this.initializer.get());
result = this.value.get();
}
return result;
}
public void setValue(final T value) {
this.value.set(value);
}
}
然后你会像这样使用这个类:
final LazyInitializer<List<String>> value = new LazyInitializer<>(ArrayList<String>::new);
这是线程安全的,可以处理setter,具有非常低的开销,尤其是:需要很少的样板代码。
现在我被迫使用Java 7,我似乎无法找到同样优雅的解决方案,因为Java 7无法使用供应商,因此需要编写大量丑陋的代码。除非您提供确切的类,否则泛型不允许实例化它们,如果您使用泛型类值(如ArrayList<String>
),则会出现问题。
据我所知,我要么被迫编写丑陋的代码,要么在我的能力范围之外做反射魔法,或者是否有任何方法可以在Java 7中以优雅的方式编写LazyInitializer
类我缺少的?
编辑:使用Jorn Vernee的答案我修改了类以使用Java 7,如下所示:
public class LazyInitializer<T> {
protected final Class<?> clazz;
protected AtomicReference<T> value = new AtomicReference<>();
public LazyInitializer(final Class<?> clazz) {
this.clazz = clazz;
}
public T get() {
T result = this.value.get();
if (result == null) {
this.value.compareAndSet(null, constructNew());
result = this.value.get();
}
return result;
}
public void setValue(final T value) {
this.value.set(value);
}
protected T constructNew() {
try {
return (T) clazz.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
throw new IllegalStateException(ex);
}
}
}
然后可以(再一次)优雅地称之为:
final LazyInitializer<List<String>> value = new LazyInitializer<>(ArrayList.class);
但是这个类不能再验证提供的类是否实际匹配(因为Generics),它只适用于默认构造函数。但至少它解决了我的问题。
答案 0 :(得分:2)
如果您可以使用Guava,则可以使用其最新的Java-7兼容版本,该版本包含Supplier
类,而该类具有与Java 8 Supplier
相同的签名。只有导入不同:com.google.common.base.Supplier
而不是java.util.function.Supplier
。
如果您不想使用Guava,您仍然可以使用与Java 8 Supplier
相同的签名编写自己的Supplier
:
public interface Supplier<T> {
T get();
}
然后,无论您使用哪个Supplier
,都可以编写以下内容:
Supplier<List<String>> stringListSupplier = new Supplier<List<String>>() {
@Override public List<String> get() { return new ArrayList<>(); }
};
如果泛型打扰你那么多,你实际上可以编写以下内容,然后随意重复使用。
public static <T> Supplier<List<T>> newArrayListSupplier() {
return new Supplier<List<T>>() {
@Override public List<T> get() { return new ArrayList<>(); }
};
}
您的最终代码将变为:
final LazyInitializer<List<String>> value = new LazyInitializer<>(MyClass.<String>newArrayListSupplier());
答案 1 :(得分:1)
lambda / method refs的一个好处是,它减少了X的代码量。所以如果你回去,当然,它会再次增加X的代码量。如果你需要在仿函数中包装任何代码,那么匿名类就是最好的方法。
使用反射还有另一种效率较低,黑客的方法。只要您按预期使用它,它就不会抛出异常。
您可以进行动态构造函数查找,但仍然需要Supplier
类型:
interface Supplier<T> {
T get();
}
然后你有一个在运行时进行查找的工厂方法:
public static <T> Supplier<T> constructorLookup(Class<?> rawtype) {
try {
Constructor<?> cons = rawtype.getConstructor();
return new Supplier<T>() {
@SuppressWarnings("unchecked")
@Override
public T get() {
try {
return (T) cons.newInstance();
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
};
} catch (NoSuchMethodException | SecurityException e) {
throw new IllegalArgumentException(e);
}
}
结果代码如下:
LazyInitializer<List<String>> value
= new LazyInitializer<>(constructorLookup(ArrayList.class));
目前这仅适用于默认构造函数,但也可以扩展为使用参数。