1

If a Java class has a field that is initialized lazily or on demand, how can we ensure that access to the lazy field is via it's initializing access method?

By way of context, we recently had a situation in which a developer added access to an object that was initialized lazily, but not via its initializing access method. This wasn't caught at compilation or in unit tests, but then caused runtime errors.

For example - in the following SSCCE, _lazyObject is initialized via the getLazyObject() method. However, if there are other methods (in the class, because it already has a private access modifier) that would want to use _lazyObject, we should access via the getLazyObject() method, as otherwise it may not have been initialized.

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...
  }
}

How can we ensure that the access of _lazyObject is via getLazyObject()?

  • Is this possible in the code within MyObject?
  • Alternatively, is it possible to ensure this via unit tests?
amaidment
  • 6,942
  • 5
  • 52
  • 88
  • You could include assertions to check `_lazyObject` has been instantiated `assert _lazyObject != null : "Use getLazyObject()!"` for the purpose of unit testing. I assume `MyObject` has a private constructor to stop other classes from instantiating `MyObject`, although this is not what your problem is about. – d.j.brown Nov 11 '16 at 10:09
  • Is the lazy object really of type Object or is it more specific? – Gimby Nov 11 '16 at 10:16
  • @Gimby - it's always more specific. We use lazy initialization quite extensively - especially when serializing classes / sharing across JVMs, so it's usually something that is too big to serialize (i.e. it's quicker to re-initialize) or something that cannot be serialized. – amaidment Nov 11 '16 at 10:25
  • @d.j.brown - MyObject is just an example for this SO question. – amaidment Nov 11 '16 at 10:25
  • @d.j.brown - can you elaborate on your suggestion? Where are you suggesting that the assertion should go, and how does that solve the problem? – amaidment Nov 11 '16 at 14:13
  • @amaidment since you stated that `_lazyObject` may be used internally rather than `getLazyObject()` as desired, then as the issue is that it may not have been instantiated (`null`) a pre condition for all methods to use the `_lazyObject` is that it is not `null`. Whilst this doesn't ensure it was accessed via `getLazyObject()` it does ensure the method is not invoked when `_lazyObject` is null, and would appear in Unit Testing with assertions enabled. This may not be sufficient, but it is just a suggestion. – d.j.brown Nov 11 '16 at 14:18
  • @d.j.brown - thanks for the suggestion. However, I don't think that's going to be sufficient - it requires that developers remember to add such an assertion, when my issue is that developers may not have remembered to use the correct access (i.e. via the intializing method). – amaidment Nov 14 '16 at 10:34

1 Answers1

0

Ok, so I'm open to further suggestions, but this is the best solution that I have come up with so far.

We can 'protect' the lazy variable in an initializing object - I thought about writing this myself, but found that there are good implementations of this in Apache Commons Lang (LazyInitializer) and Google Guava (Supplier). (Credit to Kenston Choi's answer to this question.)

For example - to clarify, I've changed the lazy object class from Object to a placeholder 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...
  }
}

However, as per the comments above - one of my main use cases is serializing/de-serializing objects containing lazy fields across JVMs. In this case, after de-serialization, the Supplier will be null. As such, we need to initialize the Supplier after deserialization.

For example, using the 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
      }
    });
  }
}
Community
  • 1
  • 1
amaidment
  • 6,942
  • 5
  • 52
  • 88