Java Stream API
Nov 11th 2020
Java and Functional Programming
Java is one of the godfathers of object-oriented programming. Java got its start with print servers, and is still very popular to this day. With applications run on the Java Virtual Machine(JVM), Developers use Java for applications across the spectrum, from Android to enterprise.
Functional programming, in contrast to object-oriented programming, an application application is composed of mathematically pure functions. You can read more about object-oriented vs functional programming here. Functions are first class members in functional programming, they are the equivalent of objects that can be passed around a common variable.
Lambda Expressions and Anonymous Functions
Anonymous functions are essentially functions without names. Lambda expressions are short hand for a anonymous function declaration. They can usually be noted for some kind of arrow syntax.
Java 8, along with other things(including the Stream API), brought us lambda expressions and somewhat easier ways to run operations asynchronously.
Lambda Syntax
In Java, the lambda expression for an anonymous function of return type void that takes no arguments, would like something like this
() -> {
//do something
//return nothing
}
This is the equivalent to
void anonFunction() {
//do something
//return nothing
}
This is the oneAdder method
int oneAdder(int addOneToMe){
return addOneToMe + 1;
}
The lambda syntax for this is
(int addOneToMe) -> {
return addOneToMe + 1;
}
Lambda and Asynchronous Processing
You might notice that our first lambda example
() -> {
//do something
//return nothing
}
Has something in common to the Runnable interface, it has the same signature as the Runnable.run() method
public void run();
We can use the Runnable interface as a functional interface(a topic for another post) to pass these lambda syntax anonymous functions around as first class members.
Back to asynchronous processing, if we look at the execute method of the Executor interface
public synchronized void execute(Runnable r);
Now we can pass the execute method a lambda expression to run in another thread
Executor executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
//do something in another thread
//return nothing
});
Java Stream API
Java 8 brought us the Stream API. The Stream API allows us to process collections in a more streamlined, syntactically cleaner fashion. The stream package provides us with a tool belt full of methods for processing collections.
A collection can be turned into a stream by calling the new stream() method.
Stream Methods
The Stream API provides quite a few handy methods, but we will concentrate on the most ones.
map
The map methods is used when you want to return a new object(can be different type) for each element in the stream.
List<String> phrases = new ArrayList<>(aPopulatedList);
List<Integer> charLengths = phrases.stream().map(String phrase -> {
return new Integer(phrase.toCharArray().length);
});
This is equal to
List<Integer> charLengths = new ArrayList<>();
for(String phrase : phrases){
charLengths.add(phrase.toCharArray().length);
}
filter
Filter allows us to remove elements from the stream, by returning a boolean value.
charLengths.stream().map(Integer length -> {
return length > 5;
});
This is equal to
List<Integer> filteredLengths = new ArrayList<>();
for(Integer length : charLengths){
if(length > 5){
filteredLengths.add(length);
}
}
forEach
Last but not least, forEach is the simplest case
charLengths.stream().forEach(Integer length -> {
length.add(1);
});
Which equals
for(Integer length : charLengths){
length+= 1;
}
Chaining
We can chain stream operations together to do more complex transformations of collections
aList.stream()
.filter(element -> {
//remove elements
})
.map(element -> {
//transform elements
})
Conclusion
So this is a micro breakdown of the Stream API to get you going. The Stream API allows us to replace our ugly for loops with much easier to read lambda expressions.
Comments