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

时间:2017-02-22 10:32:58

标签: java multithreading lazy-initialization

我正在尝试编写一个通用功能,可根据需要执行线程安全的可选延迟初始化。我不能使用标准模式,因为该值不是最终的,并且可以通过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),它只适用于默认构造函数。但至少它解决了我的问题。

2 个答案:

答案 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));

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