在 Java 7 中获取或创建模式?

Get-or-create pattern in Java 7?

我正在尝试编写一个通用功能来按需执行线程安全的可选延迟初始化。我不能使用标准模式,因为该值不是最终值,可能已经通过 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);
  }

}

然后您可以像这样使用 class:

final LazyInitializer<List<String>> value = new LazyInitializer<>(ArrayList<String>::new);

这是线程安全的,可以处理 setters,开销非常低,尤其是:需要很少的样板代码。

但是现在我不得不使用 Java 7 并且我似乎无法找到同样优雅的解决方案,因为 Java 7 不能使用供应商因此需要编写很多丑陋的代码。此外,泛型不允许实例化它们,除非您提供确切的 class,如果您使用泛型 class 值(如 ArrayList<String>),这是一个问题。

据我所知,我要么被迫编写丑陋的代码,要么进行超出我能力范围的反射魔术,或者有什么方法可以优雅地编写 LazyInitializer class Java我缺7个?

编辑: 使用 Jorn Vernee 的回答,我修改了 class 以使用 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);

但是 这个class 无法再验证所提供的class 是否真的匹配(因为泛型)并且它只适用于默认构造函数。但至少它解决了我的问题。

如果你可以使用 Guava,你可以使用其最新的 Java-7 兼容版本,其中包含一个 Supplier class,而后者又具有相同的签名为 Java 8 的 Supplier。只有导入不同:com.google.common.base.Supplier 而不是 java.util.function.Supplier.

如果您不想使用 Guava,您仍然可以编写自己的 Supplier,使用与 Java 8 的 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());

关于 lambdas/method refs 的一个好处是它减少了 X 的代码量。所以如果你回去,当然,它会再次增加 X 的代码量。如果您需要将任何代码包装在仿函数中,匿名 class 是最好的方式,恕我直言。

还有另一种效率较低、更 hackier 的方法来执行此操作,即使用反射。而且只要你按预期使用它,它就不会抛出异常。

您可以执行动态构造函数查找,您仍然需要 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));

目前这仅适用于默认构造函数,但也可以扩展为使用参数。