RFR: JDK-8312058: Documentation improvements for subscription based listeners [v6]

John Hendrikx jhendrikx at openjdk.org
Fri Aug 18 10:49:34 UTC 2023


On Fri, 11 Aug 2023 22:03:28 GMT, John Hendrikx <jhendrikx at openjdk.org> wrote:

>> Incorporates documentation review comments from #1069.
>> 
>> This PR should be kept minimal so it can be backported to JFX21; it should only contain documentation changes.
>
> John Hendrikx has updated the pull request incrementally with one additional commit since the last revision:
> 
>   Update wording on value subscriber

I think that might be better; changing docs on `Observable` and `ObservableValue` is a good idea, but may take some time to get agreement on. I would like to also make clear in such a change when you can expect listener / subscribers to fire, basically documenting the undocumented rules we already have (invalidations fire first then changes; listeners fire in the order they were added, allowing vetoing (which is used in and outside of JavaFX).

The general rules would be something like:
- Observers on super types fire before observers on sub types (ie. `Observable` before `ObservableValue`)
- Observers added on a single type fire in the order they were added (ie. value subscribers, change subscribers and change listeners are not separate lists as they're all offered by `ObservableValue`)

I've done an attempt to update the `Observable` class documentation with all recent developments:


/**
 * An {@code Observable} is an entity that wraps content and allows to
 * observe the content for invalidations.
 *
 * <h2>Lazy evaluation and events</h2>
 *
 * An implementation of {@code Observable} may support lazy evaluation,
 * which means that the content is not immediately recomputed after changes, but
 * lazily the next time it is requested. All bindings and properties in
 * this library support lazy evaluation.
 * <p>
 * Implementations of this class should strive to generate as few events as
 * possible to avoid wasting too much time in event handlers. Implementations in
 * this library mark themselves as invalid when the first invalidation event
 * occurs. They do not generate anymore invalidation events until their value is
 * recomputed and valid again.
 *
 * <h2>Observing an {@code Observable}</h2>
 *
 * Observation is possible using listeners and subscribers. These are
 * interchangeable and have the same semantics:
 * <li>Observers are notified in the order they were added</li>
 * <li>When an observer is registered more than once, it will be notified more than once</li>
 * <li>Observers may be safely registered for different {@code Observable}s</li>
 * <p>
 * When observing an {@code Observable}, it will hold a strong reference to the observer which will
 * prevent it from being garbage collected as long as the {@code Observable} is reachable.
 * If the lifecycle of the {@code Observable} is longer than a registered observer, care
 * must be taken to unregister the observer after use to avoid unwanted retention of the
 * observer, and any other objects it may refer to, in memory (also known as a memory leak).
 *
 * <h2>Unregistering observers</h2>
 *
 * Unregistering observers when their use ends can be achieved in several ways:
 * <li>For listeners, call the corresponding {@code removeListener} method, ensuring to pass the exact same instance as was used to register it</li>
 * <li>For subscribers, call {@link Subscription#unsubscribe()} on each {@link Subscription}, or on a combined subscription</li>
 * <li>If the {@code Observable} is an {@code ObservableValue}, {@link ObservableValue#when(ObservableValue)} can be used to automatically
 *     decouple the lifecycle of the observer from the original {@code ObservableValue} when a condition (like visibility) holds:
 *     <pre>
 *     {@code
 *     observableValue.when(condition).subscribe(...)
 *     }</pre>
 * </li>
 * <li>For listeners, it's possible to use a weak wrapper, like {@link WeakInvalidationListener}, to automatically unregister the listener when it becomes weakly reachable</li>
 * <p>
 * Note that the use of weak listener wrappers is no longer recommended when it can be
 * avoided as it requires intimate knowledge of how weak references work in Java and how
 * they interact with the garbage collector. The timing of when a weak listener is
 * unregistered may be very different from the intended or expected timing. It can
 * depend on many external factors, including the JVM used, its version, its (GC) settings,
 * but also on the system type and its memory availability.
 * <li>Garbage collection may be deferred for long periods (or even indefinitely), resulting in weak listeners being removed much later than expected;
 * until such time, they will keep receiving events, and hold on to any referenced objects</li>
 * <li>Weak listeners may be cleaned up almost immediately, possibly resulting in a listener being removed if care wasn't taken
 * to reference it directly</li>
 * <li>Registering the same or a similar weak listener again may see strange behavior as a previous weak listener may still be present</li>
 * <p>
 * The above can lead to hard to reproduce issues that may vary from system to system.
 * <p>
 * It is therefore highly recommended to always remove observers which observe an {@code Observable}
 * that have a longer lifecycle. This can be done either manually (in a clean up or dispose method),
 * or automatically using a condition with {@code ObservableValue#when}.
 *
 * @see javafx.beans.value.ObservableValue
 * @see javafx.collections.ObservableList
 * @see javafx.collections.ObservableMap
 *
 *
 * @since JavaFX 2.0
 */

-------------

PR Comment: https://git.openjdk.org/jfx/pull/1177#issuecomment-1683728523


More information about the openjfx-dev mailing list