2

The enclosing instance here isn't eligible for garbage collection as long as the enclosed instance is alive, right?

interface Foo<T> {
    T compute();

    default Foo<T> memoize() {
        return new Foo<>() {
            T result = null;

            boolean cached = false;

            @Override
            public T compute() {
                if (!cached) {
                    result = Foo.this.compute();
                    cached = true;
                }
                return result;
            }
        };
    }
}

Does this solve that problem?

interface Foo<T> {
    T compute();

    default Foo<T> memoize() {
        return Foo.memoize(this);
    }

    private static <T> Foo<T> memoize(Foo<T> original) {
        class Bar implements Foo<T> {
            T result = null;

            Foo<T> foo;

            Bar(Foo<T> original) {
                foo = original;
            }

            @Override
            public T compute() {
                if (foo != null) {
                    result = foo.compute();
                    foo = null;
                }
                return result;
            }
        }
        return new Bar(original);
    }
}
gdejohn
  • 7,451
  • 1
  • 33
  • 49

3 Answers3

3

Note that there’s the alternative to use lambda expressions, as lambda expressions only capture values as required, e.g.

interface Foo<T> {
    T compute();

    default Foo<T> memoize() {
        AtomicReference<Foo<T>> holder = new AtomicReference<>();
        holder.set(() -> {
               T result = compute();
               holder.set(() -> result);
               return result;
            }
        );
        return () -> holder.get().compute();
    }
}

The holder initially contains a lambda implemented Foo<T> that has a reference to the original Foo<T> instance as at will invoke compute on the first evaluation, but then, it will replace itself with a new lambda implemented Foo<T> that will invariably return the computed value. The new lambda doesn’t even need to evaluate a conditional, as the fact that the value has been computed already is implied.

Note that the holder object doesn’t have to be an AtomicReference here, but there is no canonical simple-to-use alternative. Generic arrays can’t be created, so the only alternative that comes into my mind would be a (mutable) list of size 1, e.g. created via Arrays.asList(null).

Holger
  • 285,553
  • 42
  • 434
  • 765
  • my god this is ingenious! I don't even know how to call this, a "mutating" lambda a "self-mutating" lambda? waaaay too smart – Eugene Dec 27 '17 at 14:39
1

In the first case, the anonymous inner class 'captures' a reference to the enclosing Foo by using Foo.this. As the anonymous class keeps this reference indefinitely, the enclosing Foo is not eligible for garbage collection until the anonymous class is also eligible.

In the second example, this reference is discarded after compute has been called once, so the enclosing class is eligible for garbage collection after that.

See Accessing Members of an Enclosing Class

Michael
  • 41,989
  • 11
  • 82
  • 128
0

No, why would it?

The Bar instance inside the memoize() function has a field that holds the Foo object. Whether you hold it via scoping or via implicit field assignment is irrelevant to garbage collection.

If you want to keep a reference to an object that can be garbage collected, use WeakReference/SoftReference.

Piotr Wilkin
  • 3,446
  • 10
  • 18
  • 1
    You missed the `foo = null` instruction in Bar. – JB Nizet Nov 16 '17 at 10:20
  • Would've sworn that it wasn't there a moment ago ;) yeah, you're right. In that case, after the field is set to null, the `Foo` can be garbage collected. Static inner classes work exactly like outer classes in that respect. – Piotr Wilkin Nov 16 '17 at 10:22