Java 8 Streams


Photo credits : https://www.pexels.com/photo/gray-motorcycle-chain-form-number-8-1061135/

In this blog post I'am trying to keep things simply and explain what is streams and how can we use them in code.

What is a stream

Stream is sequence of data that are conceptually produced one at a time. Streams are used to do transformation of collections. However streams are different from collections

- Streams do not have storage, it is not a data structure.
- The result produce does not modify it's source.
- Stream operations are divided into two operations as intermediate (filter, map ,sorted) and       terminal(forEach, reduce, min, max) operations to form a stream pipeline.
- A intermediate operation return a new stream and they are always lazy because intermediate operations do not perform any processing until a terminal operation is invoked on stream pipeline.

Lets see how stream operations can be used to process data.

Consider an collection of transaction objects which contains a id, amount and type. We need to extract the payment amount did via cash from these transactions in descending order , below is the implementation using java 7 and 8

Using java 7
List<Transaction> transactions = new ArrayList<>();
// filtering the payment type by cash
for (Transaction t: transactionList) {
    if (t.getType().equals("cash")) {
        transactions.add(t);    }
}
// sorting by amount
Comparator<Transaction> comparator = new Comparator<Transaction>() {
    @Override    public int compare(Transaction t1, Transaction t2) {
        return t1.getAmount() - t2.getAmount();    }
};
transactions.sort(comparator);

List<Integer> transactionAmount = new ArrayList<>();for(Transaction t: transactions){
    transactionAmount.add(t.getAmount());}

System.out.println(transactionAmount);
Using streams in java 8
List<Integer> amount = transactionList.stream()
        .filter(p -> p.getType().equals("cash"))
        .sorted(Comparator.comparing(Transaction::getAmount))
        .map(Transaction::getAmount)
        .collect(Collectors.toList());
See the below figure how the stream is being processed
Lets narrow down the above steps, 

1) First we did a filtering to our collection, we filter out the elements which having the type cash.
2) Then we did sorted the results from amount 
3) Then we only extract the field amount using the map function.
4) Finally we collect the results as a list

Let's explore few more examples.
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9 ,10);
List<Integer> evenNumbers = numbers
        .stream()
        .filter(p -> p % 2 == 0)
        .limit(4)
        .collect(Collectors.toList());

Output: [2, 4, 6, 8]

In this example we have used a short circuit (limit()) as the intermediate operations do not process until terminal operation is invoked, only the part of the stream is processed.

Filtering
You can use streams to filter or match elements in your collection.
To match elements we can use anyMatch, allMatch, and noneMatch operations, below example shows how allMatch returns true if the condition match for the all elements in the given List. This will return a boolean.
List<Integer> integers = Arrays.asList(15, 20 ,25, 30, 35, 40);
boolean greater = integers.stream().allMatch( n -> n > 10);
System.out.println(greater);
We can use findAny() and findFirst() to retrieve random elements, these return an Optional object.
Optional<Integer> optional =
integers.stream()
        .filter(t -> t > 100)
        .findAny();
Mapping
Method map can be use to transform stream of elements from one form to another. Below example shows how map is used to return length of each words from a List.
List<String> words = Arrays.asList("Stark", "Arryn", "Lannister", "Martell", "Targaryen");
List<Integer> wordLength = words.stream()
                           .map(String::length)
                           .collect(Collectors.toList());
Reduce
Reduce is a set of terminal operations such as  average(), sum(), min(), max(), and count() , these will return one value combining the values of a stream. As a example these reduction operations can be used to getting average of a values or getting the max out of values etc.
int sum = numbers.stream().reduce(0, (a, b) -> a + b);

Numeric Streams 
To overcome the overhead of Boxing operations performed, java 8 introduced three stream interfaces. 

- IntStream
- DoubleStream
- LongStream

Mostly you will use mapToInt, mapToDouble, and LongStream to convert a stream to respective versions.
int sum = transactions.stream()
                   .mapToInt(Transaction::getValue)
                   .sum();

OK, that was a very quick and basic introduction to Java Streams, in this blog post I wanted to show how these stream operations are useful to processing your collection of data. Do explore more, let's meet in another blog post.
Thank you.

Comments