Java 16 onwards, we can use Stream::mapMulti() method to produce zero, one, or multiple elements for each input element of the stream. The mapMulti() method looks quite similar to flatMap() method but they both differ in their usecases.
This Java Stream API tutorial discusses the mapMulti() method syntax and purposes with examples.
1. Syntax
Technically speaking, the mapMulti() method is a specialized form of the map() method that transforms each element of a stream into multiple output elements or none at all. Whereas map() method generally is used to produce a single output element for each input element in the stream.
Input element -> map() -> Output element
Input element -> mapMulti() -> Zero, one or multiple output elements
Here’s the syntax of the mapMulti() method which requires a BiConsumer functional interface implementation. Internally, the BiConsumer takes a Stream element ‘T’, if necessary, transforms it into type ‘R’, and invokes the downstream’s ‘Consumer::accept’.
<R> Stream<R> mapMulti(BiConsumer<T, Consumer<R>> downstream)
In the application code, we use the mapMulti() method as follows:
stream.mapMulti((inputElement, downstream) -> {
// Transformation logic
// Pass transformed or mapped elements to the downstream 'Consumer'
});
2. A Simple Example
Let’s try to understand the mapMulti() method with a simple example. In the following code, we are transforming a list of integers into a stream of their multiples (squares and cubes), thus producing multiple output elements for each input element.
import java.util.List;
public class MapMultiExample {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3);
List<Integer> multiples = numbers.stream()
.<Integer>mapMulti((num, downstream) -> {
downstream.accept(num);
downstream.accept(num * num); // Add the square of the number
downstream.accept(num * num * num); // Add the cube of the number
})
.toList();
System.out.println(multiples); // Output: [1, 1, 1, 2, 4, 8, 3, 9, 27]
}
}
We can choose to return any number of output elements as required, or not return any value at all.
3. When to use mapMulti() Method
We can use the mapMulti() method in various scenarios such as:
3.1. Data Transformation
When we transform each input element into one or multiple output elements based on certain conditions. The pseudo-code can be written as:
stream.mapMulti((inputElement, downstream) -> {
if (condition(inputElement)) {
// Transform inputElement into multiple output elements
downstream.accept(outputElement1);
downstream.accept(outputElement2);
// ...
} else {
// Optionally, pass the inputElement as is to the downstream consumer
downstream.accept(inputElement);
}
})
3.2. Flattening Nested Collections
When we have a stream of nested collections and we want to flatten them into a single stream, after checking certain conditions.
We could have used flatMap() operation but that requires an additional filter() operation for excluding the non-required nested collections. Also, flatmap() creates a Stream object internally for each nested collection, thus using more memory than mapMulti(). This is important to understand the difference between flatMap() and mapMulti() operations.
stream.mapMulti((nestedCollection, downstream) -> {
// Iterate over each element in the nested collection
if (condition(inputElement)) { // Optional condition
for (element : nestedCollection) {
if (condition(inputElement)) { // Optional condition
// Pass each element to the downstream consumer
downstream.accept(element);
}
}
}
})
4. Stream mapMulti() Example
Let us take another example close to real-world applications. Suppose we have a list of transactions. For each transaction, we have to collect the transaction alerts. But if the withdrawal transaction amount is greater than 100, then an additional fraud alert is also needed.
If we try to solve this problem using filter() and map() methods, we face the issue that one transaction can be mapped to only one event. But in our case, we need to events if there is an additional fraud alert when withdrawals are greater amount transactions.
Let us solve using the problem with mapMulti() method which serves the exact purpose i.e. returning zero, one, or multiple output elements of a single input element.
// Sample list of transactions
List<Transaction> transactions = List.of(
new Transaction("T1", 100, TransactionType.DEPOSIT),
new Transaction("T2", 50, TransactionType.WITHDRAWAL),
new Transaction("T3", 200, TransactionType.WITHDRAWAL)
);
// Process transactions and generate transaction events
List<TransactionEvent> transactionEvents = transactions.stream()
.mapMulti((transaction, downstream) -> {
if (transaction.getType() == TransactionType.DEPOSIT) {
downstream.accept(new TransactionEvent("Deposit", transaction.getAmount()));
} else if (transaction.getType() == TransactionType.WITHDRAWAL) {
downstream.accept(new TransactionEvent("Withdrawal", transaction.getAmount()));
// If withdrawal amount is greater than 100, generate an additional fraud alert event
if (transaction.getAmount() > 100) {
downstream.accept(new TransactionEvent("Fraud Alert", transaction.getAmount()));
}
}
})
.collect(Collectors.toList());
// Print the generated transaction events
transactionEvents.forEach(System.out::println);
Verify the generated events:
TransactionEvent{type='Deposit', amount=100.0}
TransactionEvent{type='Withdrawal', amount=50.0}
TransactionEvent{type='Withdrawal', amount=200.0}
TransactionEvent{type='Fraud Alert', amount=200.0}
5. Conclusion
This short Java tutorial discussed the Stream.mapMulti() method with simple-to-follow examples. We discussed its syntax, the difference between mapMulti() and map() methods, the difference between mapMulti() and flatMap() methods, and when to use mapMulti() method.
Happy Learning !!
Comments