Java 16 API Additions: Stream.toList, mapMulti and More
Introduction
Alongside records and pattern matching, Java 16Click to read tutorial: Overview of New Features in Java 16 added several methods and classes to the core libraries. This tutorial explains the Java 16 API additions with examples. We will start with two new
OUTPUT of the above code
Unlike
Stream.mapMulti()
Definition of Stream.mapMulti()
OUTPUT of the above code
Harry's order has no items, so it emits nothing. The type witness
OUTPUT of the above code
The only differences from TCP are opening the channels with
Day periods in date formatting
Java 16 added the pattern letter
OUTPUT of the above code
Long index checks in Objects
OUTPUT of the above code
Summary
In this tutorial we went through the API additions in Java 16. We collected streams with the shorter, unmodifiable
Stream methods, toList() and mapMulti(). We will then use Unix-domain socket channels for fast communication between processes on the same machine, format times with day periods such as "in the morning", and lastly look at the new long index checks in Objects. Every example is compiled for Java 16.
Stream.toList()
Collecting a stream into a list is so common that .collect(Collectors.toList()) appears in almost every codebase. Java 16 added a shortcut, stream.toList(). There is one important difference: toList() returns an unmodifiable list, while Collectors.toList() in practice returns a modifiable ArrayList -
Stream.toList() vs Collectors.toList()
//ToListDemo.java
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ToListDemo {
public static void main(String[] args) {
List<String> upper = Stream.of("tom", "harry", "sam").map(String::toUpperCase).toList();
System.out.println(upper);
List<String> modifiable = Stream.of("a", "b").collect(Collectors.toList());
modifiable.add("c");
System.out.println("Collectors.toList() allowed add: " + modifiable);
try {
upper.add("ANN");
} catch (UnsupportedOperationException e) {
System.out.println("Stream.toList() is unmodifiable: UnsupportedOperationException");
}
List<String> withNull = Stream.of("x", null).toList(); // unlike List.of(), nulls are allowed
System.out.println("Nulls allowed: " + withNull);
}
}
[TOM, HARRY, SAM] Collectors.toList() allowed add: [a, b, c] Stream.toList() is unmodifiable: UnsupportedOperationException Nulls allowed: [x, null]
List.of() and Collectors.toUnmodifiableList(), toList() accepts null elements. When replacing collect(Collectors.toList()) in existing code, check that nothing adds to or sorts the returned list afterwards.
<R> Stream<R> mapMulti(BiConsumer<? super T, ? super Consumer<R>> mapper)mapMulti() replaces each element with zero, one or more elements, like flatMap()Click to read: flatMap(). But instead of returning a new stream for every element, the mapper is given a Consumer and passes each output element to it. This avoids creating many small streams, and is often clearer when the logic is imperative, such as filtering and transforming in one step -
Replacing each element with zero or more elements
//MapMultiDemo.java
import java.util.List;
public class MapMultiDemo {
record Order(String customer, List<String> items) { }
public static void main(String[] args) {
List<Order> orders = List.of(
new Order("Tom", List.of("Book", "Pen")),
new Order("Harry", List.of()),
new Order("Sam", List.of("Laptop")));
List<String> lines = orders.stream()
.<String>mapMulti((order, downstream) -> {
for (String item : order.items()) {
downstream.accept(order.customer() + " bought " + item); // emit one element per item
}
})
.toList();
lines.forEach(System.out::println);
// Filter and convert in one step: keep only Integers among mixed objects
List<Integer> numbers = List.<Object>of(1, "two", 3, 4.0, 5).stream()
.<Integer>mapMulti((obj, downstream) -> {
if (obj instanceof Integer i) downstream.accept(i * 10);
})
.toList();
System.out.println(numbers);
}
}
Tom bought Book Tom bought Pen Sam bought Laptop [10, 30, 50]
.<String>mapMulti tells the compiler the type of the output elements, which it cannot infer from the lambda.
Unix-domain socket channels
Unix-domain sockets connect processes on the same machine through a file system path instead of an IP address and port. They are faster than TCP over the loopback interface, and access can be controlled with file permissions. Java 16 (JEP 380) added support for them to the NIO SocketChannel and ServerSocketChannel classes, on Linux, macOS and Windows 10 or later -
Server and client over a Unix-domain socket
//UnixSocketDemo.java
import java.net.StandardProtocolFamily;
import java.net.UnixDomainSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class UnixSocketDemo {
public static void main(String[] args) throws Exception {
Path socketFile = Files.createTempDirectory("uds").resolve("demo.sock");
UnixDomainSocketAddress address = UnixDomainSocketAddress.of(socketFile);
try (ServerSocketChannel server = ServerSocketChannel.open(StandardProtocolFamily.UNIX)) {
server.bind(address);
Thread client = new Thread(() -> {
try (SocketChannel channel = SocketChannel.open(address)) {
channel.write(ByteBuffer.wrap("Hello over a Unix-domain socket".getBytes(StandardCharsets.UTF_8)));
} catch (Exception e) { e.printStackTrace(); }
});
client.start();
try (SocketChannel connection = server.accept()) {
ByteBuffer buffer = ByteBuffer.allocate(100);
connection.read(buffer);
buffer.flip();
System.out.println("Server received: " + StandardCharsets.UTF_8.decode(buffer));
}
client.join();
} finally {
Files.deleteIfExists(socketFile);
}
System.out.println("Address type: " + address.getClass().getSimpleName());
}
}
Server received: Hello over a Unix-domain socket Address type: UnixDomainSocketAddress
StandardProtocolFamily.UNIX and using a UnixDomainSocketAddress. Binding creates the socket file, which must be deleted afterwards. Unix-domain sockets are commonly used to talk to local services such as Docker and databases, and between containers sharing a volume.
B to DateTimeFormatter, which formats a day period - "in the morning", "at night" - using the locale's own conventions from the Unicode CLDR data -
Formatting with the day period pattern B
//DayPeriodDemo.java
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class DayPeriodDemo {
public static void main(String[] args) {
DateTimeFormatter f = DateTimeFormatter.ofPattern("h:mm B", Locale.US);
for (LocalTime t : new LocalTime[] {LocalTime.of(8, 15), LocalTime.of(12, 0), LocalTime.of(15, 30), LocalTime.of(23, 5)}) {
System.out.println(t + " -> " + t.format(f));
}
}
}
08:15 -> 8:15 in the morning 12:00 -> 12:00 noon 15:30 -> 3:30 in the afternoon 23:05 -> 11:05 at night
Objects.checkIndex(), checkFromToIndex() and checkFromIndexSize(), added in Java 9 for int indexes, got long versions in Java 16, for code which works with large files or off-heap memory. Each returns the index if it is valid, and throws an IndexOutOfBoundsException with a clear message otherwise -
Checking long indexes
//IndexCheckDemo.java
import java.util.Objects;
public class IndexCheckDemo {
public static void main(String[] args) {
long fileSize = 5_000_000_000L;
System.out.println("Valid index: " + Objects.checkIndex(4_999_999_999L, fileSize));
try {
Objects.checkFromIndexSize(4_999_999_000L, 2_000L, fileSize);
} catch (IndexOutOfBoundsException e) {
System.out.println(e.getMessage());
}
}
}
Valid index: 4999999999 Range [4999999000, 4999999000 + 2000) out of bounds for length 5000000000
Stream.toList(), and replaced elements with zero or more elements using mapMulti(). We exchanged data between a server and a client over a Unix-domain socket channel, formatted times with the day period pattern B, and checked long indexes with Objects. Next, read the jpackage tutorialClick to read tutorial: jpackage Tutorial: Packaging Java Apps (Java 16).Continue Reading Java 12 to 17 Tutorials
Overview of New Features in Java 16Click to read tutorial: Overview of New Features in Java 16 Java 16 Records Tutorial with ExamplesClick to read tutorial: Java 16 Records Tutorial with Examples Java 16 Pattern Matching for instanceofClick to read tutorial: Java 16 Pattern Matching for instanceof jpackage Tutorial: Packaging Java Apps (Java 16)Click to read tutorial: jpackage Tutorial: Packaging Java Apps (Java 16) Strong Encapsulation of JDK Internals in Java 16 and 17Click to read tutorial: Strong Encapsulation of JDK Internals in Java 16 and 17 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 Java 16 Records Tutorial with ExamplesClick to read tutorial: Java 16 Records Tutorial with Examples Java 16 Pattern Matching for instanceofClick to read tutorial: Java 16 Pattern Matching for instanceof jpackage Tutorial: Packaging Java Apps (Java 16)Click to read tutorial: jpackage Tutorial: Packaging Java Apps (Java 16) Strong Encapsulation of JDK Internals in Java 16 and 17Click to read tutorial: Strong Encapsulation of JDK Internals in Java 16 and 17 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