【问题标题】:In Java, how do we protect access of lazy fields?在 Java 中,我们如何保护惰性字段的访问?
【发布时间】:2016-11-11 09:59:17
【问题描述】:

如果 Java 类有一个被延迟或按需初始化的字段,我们如何确保通过它的初始化访问方法访问惰性字段?

作为上下文,我们最近遇到了这样一种情况,开发人员添加了对延迟初始化的对象的访问,但不是通过其初始化访问方法。这在编译或单元测试中没有发现,但随后导致了运行时错误。

例如 - 在下面的 SSCCE 中,_lazyObject 是通过 getLazyObject() 方法初始化的。但是,如果有其他方法(在类中,因为它已经有一个private 访问修饰符)想要使用_lazyObject,我们应该通过getLazyObject() 方法访问,否则它可能没有被初始化.

public class MyObject {

  private transient volatile Object _lazyObject;

  public Object getLazyObject() {
    if (_lazyObject == null) {
      synchronized (this) {
        if (_lazyObject == null) {
          _lazyObject = new Object();
        }
      }
    }
    return _lazyObject;
  }

  public void doSomething() {
    Object a = _lazyObject; // may be null - will compile, but may cause runtime errors!
    Object b = getLazyObject(); // subject to exceptions, will not be null - this is how it should be accessed.
    // do something...
  }
}

如何保证_lazyObject的访问是通过getLazyObject()

  • 这在MyObject 中的代码中是否可行?
  • 或者,是否可以通过单元测试来确保这一点?

【问题讨论】:

  • 您可以包含断言以检查 _lazyObject 是否已实例化 assert _lazyObject != null : "Use getLazyObject()!" 以进行单元测试。我假设MyObject 有一个私有构造函数来阻止其他类实例化MyObject,尽管这不是你的问题。
  • 惰性对象真的是 Object 类型还是更具体?
  • @Gimby - 它总是更具体。我们非常广泛地使用延迟初始化——尤其是在序列化类/跨 JVM 共享时,因此它通常是太大而无法序列化(即重新初始化更快)或无法序列化的东西。
  • @d.j.brown - MyObject 只是这个 SO 问题的一个例子。
  • @d.j.brown - 你能详细说明你的建议吗?你建议断言应该去哪里,这如何解决问题?

标签: java lazy-initialization


【解决方案1】:

好的,我愿意接受进一步的建议,但这是迄今为止我提出的最佳解决方案。

我们可以在初始化对象中“保护”惰性变量 - 我曾想过自己编写这个,但发现在 Apache Commons Lang (LazyInitializer) 和 Google Guava (Supplier) 中有很好的实现。 (归功于Kenston Choi's answer to this question。)

例如 - 澄清一下,我已将惰性对象类从 Object 更改为占位符 T

public class MyObject {

  private transient Supplier<T> _lazyObject = Suppliers.memoize(new Supplier<T>() {
    @Override
    public T get() {
      return ...; // make T
    }
  });

  public T getLazyObject() {
    return _lazyObject.get();
  }

  public void doSomething() {
    Supplier<T> a = _lazyObject; // a is actually the Supplier
    // ... but we can access either via the method
    T b = getLazyObject(); 
    // or the Supplier:
    T c = _lazyObject.get();
    // do something...
  }
}

但是,根据上面的 cmets - 我的主要用例之一是跨 JVM 对包含惰性字段的对象进行序列化/反序列化。在这种情况下,反序列化后,Supplier 将变为null。因此,我们需要在反序列化后初始化Supplier

例如,使用most simple approach:

public class MyObject {

  private transient Supplier<T> _lazyObject = makeSupplier();

  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    _lazyObject = makeSupplier();
  }

  private Supplier<T> makeSupplier() {
    return Suppliers.memoize(new Supplier<T>() {
      @Override
      public Tget() {
        return ...; // make T
      }
    });
  }
}

【讨论】:

    猜你喜欢
    • 2010-10-18
    • 2016-02-29
    • 2014-11-18
    • 2016-07-19
    • 2020-11-02
    • 2016-11-19
    • 2012-09-27
    • 1970-01-01
    • 2015-05-09
    相关资源
    最近更新 更多