Introduction to Java 8 Streams
In this post, we will get a quick Introduction to Java 8 Streams API, a new feature introduced in Java 8. We will try to get a high level of understanding as what is stream and how this is useful in our day-to-day work.
Introduction
Streams are one of the most important APIs
introduced in Java 8 and completely changes the way we do processing of Collections
in Java. This is in line with the theme of Java since past few versions where advances in hardware are exploited by the language.
1. Why Java 8 Streams
Before diving into ‘What’ of Streams, let us get into ‘Why’. Why were Streams introduced and what
problem they solve? Let us see the code below:
private static HashMap<String, Integer> myIterator(List<String> inList) {
Iterator<String> thisIterator = inList.iterator();
HashMap<String, Integer> map = new HashMap<String, Integer>();
while (thisIterator.hasNext()) {
String str = thisIterator.next();
if (str.length() > 5) {
map.put(str, str.length());
}
}
return map;
}
Above code is not doing anything fancy or complicated but it takes in a List of String
, calculates the length of each String in List and puts the String, String Length in a HashMap
if length greater than 5. It is definitely solving our problem but there are multiple problems with above code.
- The code is sequential and in spite of my computer being a Quad core system, I am not exploiting computer’s processing power here.
- Simply, too much code for a simple task.
- As a programmer, I need to work on the lower level aspects of the program. I have to take care of ‘how’ the iteration takes place apart from the program logic. Such programmer drove iteration known as External Iteration. It can have optimization issues in complex and data-intensive programs.
2. Code Simplifaction using Java Streams
Java Streams help us address the above-mentioned issues by completely abstracting out the low-level parallelism logic and Iteration logic. Putting in the most simple way, Java Streams convert Collections to a Stream, process the elements in parallel by performing multiple operations and again gather the final elements into a Collection.
Java Stream API helps us do above mention by implementing internal iteration. It lets the framework perform a sequential and parallel execution, mapping elements and criteria based filtering. Java framework is in control of the resulting iteration and lets the developer fully concentrate on the data and various operations to be performed on it.
Java Stream API implements functional interfaces as its API methods, which ties up well with Lambda expressions, which is another pillar of Java 8. Together they make the Java 8 a true ‘functional’ language. Thus, when we use Java Streams, the code mentioned above turns into a single line code.
private static Map<String, Integer> myIterator(List<String> inList) {
return inList.stream().filter(str -> str.length() > 5).
collect(Collectors.toMap(str -> str, str -> str.length()));
}
Here inList is a List of Strings
. Stream
method takes the inList and streams it into a filter. The filter passes only those Strings which meet the criteria (String.length>5). Collect method takes in the filtered Strings and sends them to Collector.toMap() function which inserts the String and their Lengths in the Map. In a single line of code, we utilize the Streaming, Filtering and Collection mechanisms of the API thus writing a more efficient code.
3. Steams API Benefits
let’s see other important aspects of Streams.
Streams are on-demand Data Structures. They don’t store data and instead take in a Data Structure(mainly Collections)
as the source to produce a pipeline of that Collection. On this pipeline, desired functions can be called further to harness the power of in-memory computation.
Streams support Parallel Processing and Sequential Processing both, giving the developer options to use whatever mechanism as per the requirement. For large collections, Parallel Processing can work wonders in terms of achieving great performance. Streams support Lazy computation, that means we do these operations only when we need it and not when defined in the Streams. This leads to higher performance and optimization of the programs than the traditional methods.
Streams have two types of Operations: Intermediate and Terminal
. All those Stream operations which return a new Stream are known as intermediate operations. These never return the final result of the operations and help in building a pipeline from one operation to another. filter and map are two such operations.
All Stream operations which return a result are Terminal operations. They are eager computations which process Stream elements before returning results and they consume the Stream. After consumption by these operations, Streams are no more available for further processing. They can be identified by their return types as they would not produce any further Streams. Examples are:forEach, toArray, min, max
etc.
Summary
This was a very high level and a brief Introduction to Java 8 Streams API. We saw the benefits and features brought by Java Streams and their use to write more concise and easily readable code. Stay tuned to our website for further content on Streams and other in-depth content in Java.
Handling Exceptions generated by functions called within Streams seems to be problematic.
Do you have any advice or examples that show best practices?
Hello Ron,
I can think of following ways but it will be dependent upon particular use case
1. We can wrap method call in another one and it will give us a chance to handle it the way we want (throwing runtime exception in place of check exception etc.)
2. We can think of aggregate our exceptions.