How do I use String.indent() to format output text?

The String.indent(int n) method in Java is a useful tool for formatting multi-line strings by adjusting the amount of leading space (indentation) on each line. Introduced in Java 12, it performs the following for the specified n indentation:

  1. Positive n: Adds n spaces to the beginning of each line in the string.
  2. Negative n: Removes up to n leading spaces from each line.
  3. Zero n: Leaves the string unchanged (but trims blank lines at the start/end).

This is particularly useful for formatting text in structured views, like logs, JSON, XML, or pretty-printed output.

Example: Using String.indent to Format Output

Here is how you can use the String.indent() method:

package org.kodejava.lang;

public class StringIndentExample {
    public static void main(String[] args) {
        // Original multi-line string (no indentation)
        String text = "Line 1\nLine 2\nLine 3";

        // Adding an indent of 4 spaces
        String indented = text.indent(4);
        System.out.println("Indented by 4 spaces:\n" + indented);

        // Removing 2 leading spaces (negative indent)
        String negativeIndent = indented.indent(-2);
        System.out.println("Indented with -2 (removing spaces):\n" + negativeIndent);

        // Applying indent to empty lines
        String withEmptyLines = "Line 1\n\nLine 3";
        String indentedWithEmptyLines = withEmptyLines.indent(4);
        System.out.println("Handling empty lines:\n" + indentedWithEmptyLines);
    }
}

Explanation of Code:

  1. Adding Indentation: The first call to .indent(4) adds 4 spaces to each line.
  2. Removing Indentation: .indent(-2) deducts 2 spaces from the start of each line (if spaces exist).
  3. Empty Lines: When dealing with blank lines, indent maintains the level of indentation for such lines, adding or removing spaces as needed.

Output:

Indented by 4 spaces:
    Line 1
    Line 2
    Line 3

Indented with -2 (removing spaces):
  Line 1
  Line 2
  Line 3

Handling empty lines:
    Line 1

    Line 3

Notes:

  • Empty lines and their indentation are preserved.
  • Lines with no leading spaces are unaffected by negative indentation.
  • Leading and trailing full blank lines are removed.

When to Use:

  • To format multiline strings cleanly.
  • To produce human-readable or formatted output in tools or commands (e.g., logging, text processing).

How do I use Collectors::teeing in Streams?

In Java, Collectors::teeing is a feature introduced in Java 12 that allows you to collect elements of a stream using two different collectors and then combine the results using a BiFunction.

Syntax:

static <T, R1, R2, R> Collector<T, ?, R> teeing(Collector<? super T, ?, R1> downstream1,
                                                Collector<? super T, ?, R2> downstream2,
                                                BiFunction<? super R1, ? super R2, R> merger)

Concept:

  1. downstream1: The first collector for processing input elements.
  2. downstream2: The second collector for processing input elements.
  3. merger: A BiFunction that merges the results of the two collectors.

The teeing collector is useful when you want to process a stream in two different ways simultaneously and combine the results.


Example 1: Calculate the Average and Sum of a Stream

Here’s how you can calculate both the sum and average of a list of integers simultaneously:

package org.kodejava.util.stream;

import java.util.List;
import java.util.stream.Collectors;

public class TeeingExample {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(1, 2, 3, 4, 5);

        var result = numbers.stream()
                .collect(Collectors.teeing(
                        Collectors.summingInt(i -> i),             // Collector 1: Sum
                        Collectors.averagingInt(i -> i),           // Collector 2: Average
                        (sum, avg) -> "Sum: " + sum + ", Avg: " + avg // Merger
                ));

        System.out.println(result); // Output: Sum: 15, Avg: 3.0
    }
}

Example 2: Get Statistics (Min and Max) from a Stream

You can create a single step operation to compute the minimum and maximum values of a stream:

package org.kodejava.util.stream;

import java.util.List;
import java.util.stream.Collectors;

public class TeeingStatsExample {
    public static void main(String[] args) {
        List<Integer> numbers = List.of(3, 5, 7, 2, 8);

        var stats = numbers.stream()
                .collect(Collectors.teeing(
                        Collectors.minBy(Integer::compareTo),     // Collector 1: Get Min
                        Collectors.maxBy(Integer::compareTo),     // Collector 2: Get Max
                        (min, max) -> new int[]{min.orElse(-1), max.orElse(-1)} // Merge into an array
                ));

        System.out.println("Min: " + stats[0] + ", Max: " + stats[1]);
        // Output: Min: 2, Max: 8
    }
}

How It Works:

  • Two collectors (downstream1 and downstream2) collect the stream elements independently. For example, the first collector might compute the sum, while the second computes the average.
  • Once the stream has been fully processed, the results from both collectors are passed to the merger, which applies a transformation or combination of the two results.

Example 3: Concatenate Strings and Count Elements Simultaneously

Here’s how you can process a stream of strings to count the number of elements and also concatenate them into a single string:

package org.kodejava.util.stream;

import java.util.List;
import java.util.stream.Collectors;

public class TeeingStringExample {
    public static void main(String[] args) {
        List<String> names = List.of("Alice", "Bob", "Charlie");

        var result = names.stream()
                .collect(Collectors.teeing(
                        Collectors.joining(", "),        // Collector 1: Concatenate strings
                        Collectors.counting(),           // Collector 2: Count elements
                        (joined, count) -> joined + " (Total: " + count + ")" // Merge
                ));

        System.out.println(result);  // Output: Alice, Bob, Charlie (Total: 3)
    }
}

Key Points:

  1. Stream Processing: The stream elements are processed only once but collected using two different collectors.
  2. Merger Function: The merger combines both results into a final result of your choice.
  3. Utility: Collectors::teeing is very useful when you need to perform dual aggregations in one pass over the data.

Now you’re ready to use Collectors::teeing for combining results in your streams!