Skip to main content
Java

Java 22: Stream Gatherers and Class-File API

Ravinder··6 min read
JavaStream GatherersClass-File APIJava 22
Share:
Java 22: Stream Gatherers and Class-File API

Java 22: Stream Gatherers and Advanced APIs

Java 22 (March 2024) introduces stream gatherers for sophisticated stream operations and a public class-file API for bytecode manipulation. It's a non-LTS release with powerful tools for advanced developers.

1. Stream Gatherers (Preview)

Compositional stream transformations that go beyond traditional stream operations.

Before Gatherers:

// Complex stream operations were limited
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
 
// Windowing - had to write custom code
List<List<Integer>> windows = new ArrayList<>();
for (int i = 0; i < numbers.size() - 1; i++) {
    windows.add(numbers.subList(i, i + 2));
}
 
// Grouping consecutive items
// Complex manual logic required

With Gatherers (Java 22+):

// Powerful stream transformations
import java.util.stream.Gatherers;
 
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
 
// Windowing operation
var windows = numbers.stream()
    .gather(Gatherers.windowSliding(2))
    .toList();
// Result: [[1,2], [2,3], [3,4], ..., [9,10]]
 
// Windowing with fixed size
var fixedWindows = numbers.stream()
    .gather(Gatherers.windowFixed(3))
    .toList();
// Result: [[1,2,3], [4,5,6], [7,8,9], [10]]
 
// Custom aggregation
var evenOddGroups = numbers.stream()
    .gather(Gatherers.groupingBy(n -> n % 2 == 0 ? "even" : "odd"))
    .toList();
 
// Scanning with state
var sums = numbers.stream()
    .gather(Gatherers.scan(() -> 0, (acc, val) -> acc + val))
    .toList();
// Result: [0, 1, 3, 6, 10, 15, ...]
 
// String tokenization
String text = "apple,banana,cherry,date";
var tokens = text.stream()
    .gather(Gatherers.scan(
        () -> new StringBuilder(),
        (sb, c) -> {
            if (c == ',') sb.delete(0, sb.length());
            else sb.append(c);
            return sb;
        }
    ))
    .filter(s -> !s.isEmpty())
    .toList();

Custom Gatherers:

// Creating custom gatherers
public class CustomGatherers {
    
    // Gatherer for batching
    static <T> Gatherer<T, ?, List<T>> batching(int batchSize) {
        return Gatherer.of(
            // Supplier: create accumulator
            () -> new BatchAccumulator<T>(batchSize),
            // Integrator: process element
            (acc, element, downstream) -> {
                acc.add(element);
                if (acc.isFull()) {
                    downstream.push(acc.getBatch());
                    acc.reset();
                }
                return true;
            },
            // Finisher: flush remaining
            (acc, downstream) -> {
                if (!acc.isEmpty()) {
                    downstream.push(acc.getBatch());
                }
            }
        );
    }
    
    static class BatchAccumulator<T> {
        final int batchSize;
        final List<T> batch = new ArrayList<>();
        
        BatchAccumulator(int size) { this.batchSize = size; }
        void add(T item) { batch.add(item); }
        boolean isFull() { return batch.size() == batchSize; }
        boolean isEmpty() { return batch.isEmpty(); }
        List<T> getBatch() { return new ArrayList<>(batch); }
        void reset() { batch.clear(); }
    }
}
 
// Using custom gatherer
var batches = numbers.stream()
    .gather(CustomGatherers.batching(3))
    .toList();

2. Class-File API (Preview)

Programmatic manipulation of Java class files.

// Reading class files
import java.lang.classfile.*;
import java.Lang.classfile.attribute.*;
 
public class ClassFileReader {
    
    public static void readClassInfo(Path classPath) throws IOException {
        ClassFile cf = ClassFile.of().read(classPath);
        
        // Get class information
        System.out.println("Class: " + cf.thisClass().asSymbol());
        System.out.println("Superclass: " + cf.superclass().asSymbol());
        
        // List methods
        for (MethodModel method : cf.methods()) {
            System.out.println("Method: " + method.methodName() + 
                             method.methodType());
        }
        
        // List fields
        for (FieldModel field : cf.fields()) {
            System.out.println("Field: " + field.fieldName() + " " +
                             field.fieldType());
        }
    }
    
    // Creating class files programmatically
    public static void createSimpleClass() throws IOException {
        var cv = ClassModel.of()
            .withVersion(65, 0) // Java 21
            .withFlags(ClassFile.ACC_PUBLIC | ClassFile.ACC_FINAL)
            .withThisClass("com/example/Generated")
            .withSuperclass(ClassDesc.of("java.lang.Object"))
            .withMethod(MethodModel.of()
                .withFlags(ClassFile.ACC_PUBLIC)
                .withName("<init>")
                .withDescriptor("()V")
                .build()
            )
            .build();
        
        // Write to file
        Files.write(
            Paths.get("Generated.class"),
            cv.toByteArray()
        );
    }
}

Dynamic Proxy Generation:

// More powerful than java.lang.reflect.Proxy
public class DynamicProxyGenerator {
    
    public static Object createProxy(Class<?> interfaceClass) {
        return ClassFile.of().build(
            ClassDesc.of("Proxy$" + System.identityHashCode(interfaceClass)),
            classBuilder -> {
                classBuilder.withFlags(ClassFile.ACC_PUBLIC);
                classBuilder.withSuperclass(ClassDesc.of("java.lang.Object"));
                classBuilder.withInterfaceSymbols(
                    ClassDesc.of(interfaceClass.getName())
                );
                
                // Generate proxy methods
                for (Method m : interfaceClass.getMethods()) {
                    classBuilder.withMethod(generateMethod(m));
                }
            }
        );
    }
    
    static MethodModel generateMethod(Method m) {
        return MethodModel.of()
            .withFlags(ClassFile.ACC_PUBLIC)
            .withName(m.getName())
            .build();
    }
}

3. String Interpolation Enhancements

Further refinement to string templates.

// String templates with format specifiers (Preview)
String name = "Alice";
int score = 95;
 
// STR template (simple interpolation)
String msg1 = STR."Hello \{name}, score: \{score}";
 
// TEMPLATE string with expressions
String msg2 = STR."""
    Name: \{name}
    Score: \{score}
    Grade: \{score >= 90 ? "A" : "B"}
    """;
 
// Logging with templates
var log = STR."User \{name} logged in with score \{score}";
// Deferred evaluation for better performance

4. Regular Expression Named Groups

Better regex support.

// Named groups in regex patterns
String email = "alice@example.com";
Pattern emailPattern = Pattern.compile(
    "(?<name>\\w+)@(?<domain>\\w+\\.\\w+)"
);
 
Matcher matcher = emailPattern.matcher(email);
if (matcher.find()) {
    String username = matcher.group("name");          // alice
    String domain = matcher.group("domain");          // example.com
    System.out.println(username + " at " + domain);
}

5. Launch Multi-File Source-Code Programs

Run Java programs with multiple source files without compilation.

# Before Java 11: Had to compile first
javac Main.java Utils.java
java Main
 
# Java 22+: Run multiple files directly
java Main.java Utils.java
 
# Or entire directory
java src/

Practical Example:

// Main.java
public class Main {
    public static void main(String[] args) {
        System.out.println(Utils.getMessage());
    }
}
 
// Utils.java
public class Utils {
    public static String getMessage() {
        return "Hello from Utils!";
    }
}
 
// Run without compilation
// java Main.java Utils.java

6. Unnamed Variables (Finalization)

Underscore for explicitly unused variables.

// Using underscore for unused variables
for (var _ : list) {
    System.out.println("Iteration");
}
 
// Catching but ignoring exceptions
try {
    riskyOperation();
} catch (Exception _) {
    // Intentionally ignored
}
 
// Lambda parameters
list.forEach((_x, _y) -> process());
 
// Record patterns
if (point instanceof Point(_, var y)) {
    System.out.println("Y: " + y);
}

Developer Impact

Powerful Tools:

  • Gatherers for sophisticated stream operations
  • Class-file API for bytecode generation
  • Better regex and string handling
  • Simpler script-like Java execution

Challenges:

  • Learning curve for advanced APIs
  • Class-file API is low-level
  • Gatherers require functional thinking

Pros and Cons

Pros ✅

  • Stream Gatherers: Powerful compositional operations
  • Class-File API: Safer than bytecode libraries
  • Flexibility: Fine-grained control over streams
  • Regex Improvements: Named groups simplify patterns
  • Script Support: Run Java like Python scripts
  • Unnamed Variables: Clearer intent

Cons ❌

  • Complex APIs: Class-file API steep learning curve
  • Preview Features: May change
  • No LTS: 6-month support window
  • Performance: Dynamic class generation overhead
  • Advanced Users Only: Not for typical development

Real-World Example

// Stream processing with gatherers
public class DataProcessor {
    
    public void processStream(Stream<Integer> data) {
        // Complex stream transformation
        var result = data
            .gather(Gatherers.windowFixed(10)) // Batch by 10
            .map(batch -> batch.stream()
                .mapToInt(Integer::intValue)
                .sum()
            )
            .gather(Gatherers.scan(
                () -> 0L,
                (sum, value) -> sum + value
            ))
            .filter(value -> value > 1000)
            .toList();
        
        System.out.println("Processed: " + result);
    }
}

Conclusion

Java 22 provides powerful APIs for advanced use cases. Stream gatherers make complex data transformations elegant. The class-file API democratizes bytecode manipulation. While non-LTS and developer-focused, Java 22 shows Java's continued evolution.

Recommendation:

  • Explore if working with streams heavily
  • Use for dynamic class generation if needed
  • Don't adopt for production without Java 21 LTS as foundation
  • Expect some features to be finalized in Java 23+