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
OUTPUT of the above code
Explanation of the code
Collectors.teeing()
Definition of Collectors.teeing()
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.
OUTPUT of the above code
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()
OUTPUT of the above code
CompactNumberFormat
Dashboards and social media show large numbers in a short, readable form - "1.2K" followers, "3M" downloads. Java 12 added
OUTPUT of the above code
Explanation of the code
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);
}
}
if (ready) {
start();
}
deep
less
"JAVA 12"
length = 7
code.indent(4)indents all three lines by 4 spaces, keeping the extra indentation ofstart();. Because the result always ends with a line ending, it is printed withprint()rather thanprintln().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 anyFunction<String, R>, so it can turn a string into another type - here anint. It is most useful for applying your own utility methods without breaking a chain of calls.
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)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);
}
}
Average: 77.6 Range: 64 to 91
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);
}
}
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, 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));
}
}
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
- 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.
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.Continue Reading Java 12 to 17 Tutorials
Overview of New Features in Java 12Click to read tutorial: Overview of New Features in Java 12 Overview of New Features in Java 13Click to read tutorial: Overview of New Features in Java 13 Overview of New Features in Java 14Click to read tutorial: Overview of New Features in Java 14 Overview of New Features in Java 15Click to read tutorial: Overview of New Features in Java 15 Overview of New Features in Java 16Click to read tutorial: Overview of New Features in Java 16 Overview of New Features in Java 17Click to read tutorial: Overview of New Features in Java 17 Java Switch Expressions: Arrow Labels and yieldClick to read tutorial: Java Switch Expressions: Arrow Labels and yield Java 14 Helpful NullPointerExceptions ExplainedClick to read tutorial: Java 14 Helpful NullPointerExceptions Explained Java 14 JFR Event Streaming Tutorial with ExamplesClick to read tutorial: Java 14 JFR Event Streaming Tutorial with Examples
Overview of New Features in Java 12Click to read tutorial: Overview of New Features in Java 12 Overview of New Features in Java 13Click to read tutorial: Overview of New Features in Java 13 Overview of New Features in Java 14Click to read tutorial: Overview of New Features in Java 14 Overview of New Features in Java 15Click to read tutorial: Overview of New Features in Java 15 Overview of New Features in Java 16Click to read tutorial: Overview of New Features in Java 16 Overview of New Features in Java 17Click to read tutorial: Overview of New Features in Java 17 Java Switch Expressions: Arrow Labels and yieldClick to read tutorial: Java Switch Expressions: Arrow Labels and yield Java 14 Helpful NullPointerExceptions ExplainedClick to read tutorial: Java 14 Helpful NullPointerExceptions Explained Java 14 JFR Event Streaming Tutorial with ExamplesClick to read tutorial: Java 14 JFR Event Streaming Tutorial with Examples