In this article you’ll learn what Streams are and how to use them in Android development, even if, in theory, you cannot use them in Android development. O_O
Since the Java 8 release (march 2014), android developers are struggling
to find a way to replicate all (or at least some) of the new awesome patterns
provided by Java 8.
Unfortunately Android doesn’t support Java 8 yet; Java 7 (v1.7) is supported
on the KitKat edition (Android 4.4.+) and above.
So farewell Lambda expressions…
…
…
or not?
Of course not.
Some brilliant minds came with a super solution to this stumbling block:
RETROLAMBDA
From
now on even sad lambda-less android developer like us could use the arrow
‘->’ method in our code!
As a warm up, let’s see quickly how to use Retrolambda inside our projects (it’s assumed that you know the AndroidStudio gradle build system and how it works):
- In the ./build.gradle file add a new classpath:
2. In the ./app/build.gradle file apply the Retrolambda plugin:
3. Inside this file, also add:
4. Sync the Project
Now you’re ready to use all the lambdas in the world! NOT BAD.
Ok, retrolambda bla bla bla…but what is a Stream?
A stream is an abstraction for specifying aggregate computations on a DataSet
Basically streams allow us to write collections-processing code at a higher
level of abstraction using a functional approach.
A common pattern for Java developers when working with collections is
to iterate over a collection, operating on each element in turn. We do
that on a daily bases: in my life I thin I wrote the ‘for’ word on the
IDE much more times than I ate a pizza (and I’m italian!). So iterate!
Iterate! ITERATE!
For (ah-ah-ah) example, if we want to find John Snow in all of the characters of the ‘A Song of Ice and Fire’ books and set that “He knows nothing”, we would write the code below:
Of course it involves a lot of boilerplate code. Every time you want to
iterate over a collection, filtering it and do stuff on each item, you
had to write this ugly code. In addition, the for-loop structure is difficult
to comprehend until we reach the body of the loop. For a single loop things
are fine, but when you have multiple loops it becomes hard to understand.
How to make the code more readable and easy to write?
This is where the Java 8 Stream interface comes into play.
But, wait, we’re talking about Android (only Java 7 remember?). :(
Fortunately, another clever developer rolled up his sleeves and released
a Java 5, 6, 7 porting of Streams: Lightweight-Stream-API
Just add this line to your ./app/build.gradle file...
…and voilà, le jeux sont fait.
Let’s see how Stream works.
Much more cleaner than before!
The Stream API introduces — roll the drums — the concept of Stream class.
What we do before is called a STREAM PIPELINE, which is composed of three
distinct parts:
1. a source (the Collection -> Stream conversion line of
code)
2. zero or more intermediate operations (filter, map, distinct,
sorted)
3. a terminal operation
The ‘intermediate operations’
are lazy because they don’t execute anything until the terminal operation
starts. They just set up the pipeline.
The Stream code example is very clean but we can do better and take advantage
of Lambda expression’s Type inference. Type inference enables
a compiler to deduce the type of a particular expression automatically
when it compiles your code, simply by examining the values you provide.
Here’s the new code:
The class name inside operations are gone!
For anyone who has just woken up and can’t spot the differences we are
passed from “(Person person) -> …” to “person -> …”. This is the magic
of type inference.
Maybe the most difficult part of the Stream pattern is to understand what
all the intermediate operations do. Some of them are intuitive (filter,
distinct, sorted), others may lead to a bit of headache.
1. map
I’m confident that you have been writing map
operations many times without recognize them.
Every time you take a list of Integer and converts them to their String
equivalent, you are performing a map operation.
A precise definition of map is:
If you’ve got a function that converts a value of one type into another, map lets you apply this function to a stream of values, producing another stream of the new values. (Java 8 Lambdas — Richard Warburton)
2. flatMap
Sometimes you want a different type of
map in which you create a new Stream object as the replacement, instead
of a new value: this is where flatMap could helps.
Above you can observe a so-called “marble diagram”, a graphic representation of the stream. This kind of diagram was invented by the developers behind the ReactiveX project. These workflows are born to explain the RxJava Observables-Observer process but we can use it for the flatMap explanation. This intermediate operation allows to replace a value with a Stream and chains all the streams together.
Here we convert the stream of Person items in a Stream of Crow items with flatMap. After that we convert the stream into a List of Crow items.
In some cases, we may have the need to reuse a filtering function, a mapping
function or a sorting function throughout the code.
Here is the moment when an other feature of Java 8 comes in handy: the ::
method reference.
Let’s say we had to compare the Night’s Watch rangers multiple times in
multiple sorting operations. An easy solution is to replicate always the
same code in every sorted() operation.
But there’s a better way to do this. Look at example below:
First we create a method in the class we want to use the streams.
Once you did that, you can replace the sorted() content with:
With the class::method notation we can reuse our functions in an easy way.
Although Java 8's stream and its LSA counterparts work in the same way
under the hood, there are some slightly differences between them.
In the examples above you can see that the creation of the stream starts
with:
Stream.of(YourCollection)
In the Java 8 implementation you’ll
see YourCollection.stream(…) instead.
Either way an
instance of Stream
is created.
Another small difference concernes the sorting operation. In Java 8, the
operation uses the function ‘sort()’. In Lightweight-Stream-API, the function
is called ‘sorted()’ but they work exactly in the same way.
That’s it. You have reach the bottom of this TL;DR article.
I described only some of the features of Java 8 and LSA. I’ve to study
this subject because every day I found something new and awesome. There
are so many enhancement to learn and try in our code!
I’d love to hear about how you’ve implemented some Stream operations,
anything new you have discovered about these arguments and any questions
that this article rise up.