Java 12 API Additions: String.indent, teeing, mismatch

Introduction Besides its JDK Enhancement Proposals, Java 12Click to read tutorial: Overview of New Features in Java 12 added several small methods to the core libraries which remove everyday boilerplate. This tutorial explains the Java 12 API additions with examples. We will start with two new String methods, indent() and transform(). We will then compute two results in a single stream pass with Collectors.teeing(), compare files with Files.mismatch(), and lastly format numbers in a compact form such as "1.2K" with CompactNumberFormat. Every example is compiled for Java 12. String.indent() and String.transform() Definitions of the new String methods
public String indent(int n)
public <R> R transform(Function<? super String, ? extends R> f)
indent(n) adds n spaces to the start of every line of a string, or removes up to -n leading whitespace characters from every line if n is negative. It also normalizes line endings to \n, and ends the result with a line ending. transform(f) simply applies a function to the string and returns the result - which lets you chain your own functions in the middle of a chain of String methods.
String.indent() and String.transform()
//IndentTransformDemo.java
import java.util.function.Function;

public class IndentTransformDemo {
  public static void main(String[] args) {
    String code = "if (ready) {\n  start();\n}";
    System.out.print(code.indent(4));          // add 4 spaces to each line
    System.out.print("        deep\n    less".indent(-4));   // remove up to 4 spaces

    Function<String, String> quote = s -> "\"" + s + "\"";
    String result = "  Java 12  "
        .strip()
        .transform(quote)                    // apply our own function in the chain
        .transform(String::toUpperCase);
    System.out.println(result);
    int length = "Java 12".transform(String::length);   // transform can change the type
    System.out.println("length = " + length);
  }
}
 OUTPUT of the above code
    if (ready) {
      start();
    }
    deep
less
"JAVA 12"
length = 7
Explanation of the code
  • code.indent(4) indents all three lines by 4 spaces, keeping the extra indentation of start();. Because the result always ends with a line ending, it is printed with print() rather than println().
  • indent(-4) removes 4 spaces from " deep" and all 4 from " less". If a line has fewer leading spaces than requested, all of them are removed.
  • transform() takes any Function<String, R>, so it can turn a string into another type - here an int. It is most useful for applying your own utility methods without breaking a chain of calls.
Collectors.teeing() Definition of Collectors.teeing()
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)
Sometimes you need two results from the same data - say, the count of items and their total, to compute an average, or the minimum and the maximum. Before Java 12 this meant streaming the data twice, or writing a custom collector. Collectors.teeing() sends every element to two collectors at once, like a T-shaped pipe fitting, and combines their two results with a merger function. If you are new to collectors, read the Collector basics tutorialClick to read: the Collector basics tutorial first.
Two results in one pass with Collectors.teeing()
//TeeingDemo.java
import java.util.List;
import java.util.stream.Collectors;

public class TeeingDemo {
  public static void main(String[] args) {
    List<Integer> marks = List.of(78, 91, 64, 85, 70);

    double average = marks.stream().collect(Collectors.teeing(
        Collectors.summingInt(m -> m),          // collector 1: the total
        Collectors.counting(),                  // collector 2: the count
        (total, count) -> (double) total / count));
    System.out.println("Average: " + average);

    String range = marks.stream().collect(Collectors.teeing(
        Collectors.minBy(Integer::compare),
        Collectors.maxBy(Integer::compare),
        (min, max) -> min.get() + " to " + max.get()));
    System.out.println("Range: " + range);
  }
}
 OUTPUT of the above code
Average: 77.6
Range: 64 to 91
In each call, the first two arguments are ordinary collectors, and the third receives both of their results. The stream is traversed only once, which matters when the data comes from a source which cannot be read twice, such as a file or a network stream. Files.mismatch() Files.mismatch(path1, path2) compares two files and returns the position of the first byte which differs, or -1L if the files are identical. If one file is a prefix of the other, it returns the length of the shorter file.
Comparing files with Files.mismatch()
//MismatchDemo.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

public class MismatchDemo {
  public static void main(String[] args) throws IOException {
    Path a = Files.writeString(Files.createTempFile("a", ".txt"), "Java 12 release notes");
    Path b = Files.writeString(Files.createTempFile("b", ".txt"), "Java 12 release notes");
    Path c = Files.writeString(Files.createTempFile("c", ".txt"), "Java 13 release notes");

    System.out.println("a vs b: " + Files.mismatch(a, b));   // identical
    System.out.println("a vs c: " + Files.mismatch(a, c));   // differ at index 6
    for (Path p : new Path[] {a, b, c}) Files.delete(p);
  }
}
 OUTPUT of the above code
a vs b: -1
a vs c: 6
"Java 12" and "Java 13" first differ at the character '2'/'3', at index 6. The method compares bytes efficiently, without reading whole files into memory, so it works for large files too.
CompactNumberFormat Dashboards and social media show large numbers in a short, readable form - "1.2K" followers, "3M" downloads. Java 12 added CompactNumberFormat, created with NumberFormat.getCompactNumberInstance(locale, style), where the style is SHORT ("1K") or LONG ("1 thousand"). The formats come from the locale data, so they are correct for each language.
Formatting numbers compactly
//CompactNumberDemo.java
import java.text.NumberFormat;
import java.util.Locale;

public class CompactNumberDemo {
  public static void main(String[] args) {
    NumberFormat shortFormat = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
    NumberFormat longFormat = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.LONG);
    shortFormat.setMaximumFractionDigits(1);

    for (long n : new long[] {950, 1_200, 15_500, 3_400_000, 7_000_000_000L}) {
      System.out.printf("%,15d -> %-6s %s%n", n, shortFormat.format(n), longFormat.format(n));
    }
    NumberFormat german = NumberFormat.getCompactNumberInstance(Locale.GERMANY, NumberFormat.Style.LONG);
    System.out.println("German: " + german.format(3_400_000));
  }
}
 OUTPUT of the above code
            950 -> 950    950
          1,200 -> 1.2K   1 thousand
         15,500 -> 15.5K  16 thousand
      3,400,000 -> 3.4M   3 million
  7,000,000,000 -> 7B     7 billion
German: 3 Millionen
Explanation of the code
  • By default, compact formats show no decimal places, so 1,200 becomes "1K". setMaximumFractionDigits(1) allows one, giving "1.2K".
  • The long style writes out the unit, and the German locale uses its own words and rounding - which is why it is better to use the locale data than to build such strings yourself.
  • The output above is from a recent JDK; the exact texts come from the Unicode CLDR locale data, which is updated over time.
Summary In this tutorial we went through the library additions in Java 12. We indented multi-line text with String.indent() and chained our own functions with String.transform(). We computed an average and a range in a single stream pass with Collectors.teeing(), compared files with Files.mismatch(), and formatted large numbers compactly with CompactNumberFormat. Next, read the overview of new features in Java 13Click to read tutorial: Overview of New Features in Java 13.