Gatherers4j

repository·main·Indexed 41 days ago

https://github.com/tginsberg/gatherers4j

A library of custom Stream Gatherers (intermediate operations) for Java 25+. It extends the Java Stream API with utility operations for sequence manipulation, filtering, grouping, validation, and mathematical processing, including moving averages, running aggregates, and windowing operations. All gatherers are accessed through the Gatherers4j class.

Tokens
57.2K
Snippets
193
Records
223
Agent score
50%

What's inside Gatherers4j

  1. Overview of Gatherers4j

    main

    Gatherers4j is a library designed to extend the Java Stream API by providing custom intermediate operations, known as Gatherers.

    While Java's standard Stream API allows for custom terminal operations via Collectors, it lacks a convenient way to implement custom intermediate operations. Gatherers4j fills this gap, allowing developers to plug in new sequence manipulations, filtering, grouping, or mathematical operations directly into their stream pipelines.

    Key characteristics:

    • Single Entrypoint: All gatherers are accessed through the Gatherers4j class.
    • Minimal Dependencies: The library has only one tiny dependency, JSpecify, used for nullability annotations to assist static type checkers.
    • License: Apache 2.0.
  2. Understand the foundation of Stream Gatherers via JEP-485

    main
    Gatherers4j is built upon the concepts introduced in JEP-485, the JDK Enhancement Proposal that delivered Stream Gatherers to the Java ecosystem. To understand the design rationale, the necessity of Gatherers, and the evolution of the API, refer to the JEP documentation. For historical context, you can also review the previous previews: JEP-473 (Second Preview) and JEP-461 (First Preview).
  3. How to handle nulls in movingGeometricMean()

    main

    By default, movingGeometricMean() ignores null values. You can change this behavior using one of the following methods:

    1. Ignore (Default): null values are skipped.
    2. Treat as One: Use .treatNullAsOne() to treat null as BigDecimal.ONE.
    3. Custom Replacement: Use .treatNullAs(BigDecimal replacement) to treat null as a specific value.

    Note: treatNullAsZero() is also available but is generally only useful in very specific circumstances.

    // Treating null as one
    Stream
        .of(null, null, new BigDecimal("8"), new BigDecimal("64"))
        .gather(Gatherers4j.movingGeometricMean(3).treatNullAsOne())
        .toList();
    
    // [ 
    //   BigDecimal("1"), 
    //   BigDecimal("1"), 
    //   BigDecimal("2"), 
    //   BigDecimal("8") 
    // ]
  4. Configure partial values in moving gatherers

    main

    Starting from version v0.13.0, all "moving" gatherers (such as movingMax(), movingMin(), movingSum(), simpleMovingAverage(), etc.) include all calculated values by default, even before the full lookback window has been reached.

    To suppress these partial results and only emit values once the full window is available, use the excludePartialValues() method.

    // Example behavior change in v0.13.0+
    // Default: includes partial values
    stream.gather(movingSum(windowSize))
    
    // To exclude partial values:
    stream.gather(movingSum(windowSize).excludePartialValues())
  5. Understand the categories of Gatherers

    main

    Gatherers in this library are organized into five functional categories to help you select the right operation for your stream processing needs:

    • Sequence Operations: Used to reorder, combine, or manipulate the sequence of elements.
    • Filtering and Selection: Used to select or remove elements based on specific criteria.
    • Grouping and Windowing: Used to collect elements into groups or windows.
    • Mathematical Operations: Used to perform calculations over the stream.
    • Validation and Constraints: Used to enforce specific conditions on the stream.
  6. Follow the naming conventions for Gatherers4j

    main

    When implementing or using Gatherers4j, follow these naming patterns to ensure consistency with the library's API surface:

    • Imperative Tense: Use imperative verbs for method names (e.g., use group() instead of grouping() or grouped()).
    • ...By() suffix: Use this pattern when the operation is performed on the output of a provided function (typically a Function or Comparator).
    • ensure...() prefix: Use this pattern for operations that validate a condition in the stream and fail if the condition is not met.
    • ...Indexed() suffix: Use this pattern for operations that include the zero-based index of the element in the stream.
  7. Categories of Gatherers in Gatherers4j

    main

    Gatherers in this library are organized into five functional categories to help you find the right intermediate operation for your stream pipeline:

    1. Sequence Operations: Used to reorder, combine, or manipulate the sequence of elements.
    2. Filtering and Selection: Used to select or remove elements based on specific criteria.
    3. Grouping and Windowing: Used to collect elements into groups or windows.
    4. Mathematical Operations: Used to perform calculations over the stream.
    5. Validation and Constraints: Used to enforce specific conditions on the stream.
  8. Handle null values in simpleMovingAverageBy()

    main

    The simpleMovingAverageBy() gatherer allows you to define how null values in the input stream should be treated.

    1. Default Behavior: Nulls are ignored and play no part in calculations.
    2. Treat as Zero: Use .treatNullAsZero() to treat null as BigDecimal.ZERO.
    3. Custom Replacement: Use .treatNullAs(BigDecimal replacement) to treat null as a specific value.
    // Example: Treating null as zero
    Stream
        .of(
            new NamedValue("first",  null),
            new NamedValue("second", null),
            new NamedValue("third",  new BigDecimal("10.0")),
            new NamedValue("fourth", new BigDecimal("20.0")),
            new NamedValue("fifth",  new BigDecimal("30.0"))
        )
        .gather(Gatherers4j.simpleMovingAverageBy(3, NamedValue::value).treatNullAsZero())
        .toList();
    
    // Result: [0, 0, 3.333333333333333, 10.0, 20.0]
  9. Handle null values in movingProductBy()

    main

    When the mappingFunction returns null, you can control how these values affect the moving product:

    1. Default (Ignore): null values are ignored and play no part in calculations.
    2. Treat as One: Use .treatNullAsOne() to treat null as BigDecimal.ONE.
    3. Custom Replacement: Use .treatNullAs(BigDecimal replacement) to treat null as a specific value.

    Note: treatNullAsZero() is also available but is rarely recommended for product calculations.

    // Example: Treating null as TWO
    Stream
        .of(
            new NamedValue("first",  null),
            new NamedValue("second", null),
            new NamedValue("third",  new BigDecimal("10.0")),
            new NamedValue("fourth", new BigDecimal("20.0")),
            new NamedValue("fifth",  new BigDecimal("30.0"))
        )
        .gather(Gatherers4j.movingProductBy(3, NamedValue::value).treatNullAs(BigDecimal.TWO))
        .toList();
    
    // Result: [2, 4, 40.0, 400.00, 6000.000]
  10. Install Gatherers4j via Maven or Gradle

    main

    To use Gatherers4j, add it as a dependency to your Java project. The library requires Java 25+ and has one transitive dependency: the JSpecify annotations for static analysis.

    Maven

    Add the following to your pom.xml:

    Gradle

    Add the following to your build.gradle or build.gradle.kts:

    <!-- Maven -->
    <dependency>
        <groupId>com.ginsberg</groupId>
        <artifactId>gatherers4j</artifactId>
        <version>0.14.0</version>
    </dependency>
    
    <!-- Gradle -->
    implementation("com.ginsberg:gatherers4j:0.14.0")