Unleash the Power of Java 8 Stream
Join the DZone community and get the full member experience.
Join For FreeIn this post we are going to unleash the power of Java 8 Stream API. One important thing to remember is, almost all the changes introduced in Java 8 are intended to help developers write better code.
Two major features introduced in Java 8 are Lambda expressions and Stream API which uses Lambdas heavily. With these features Java have come very close to Functional Programming.
In this post we will concentrate on what can be achieved using Java Stream API
So without any further ado lets get into Stream.
All of us have written code similar to this to iterate over a collection and process each element.
Employee e1=new Employee("Alex Tudor","Senior Developer","2014-02-16"); Employee e2=new Employee("John Michael","Research Analyst","2011-08-20"); Employee e3=new Employee("Anna Stark","Web Designer","2009-09-15"); List<Employee> empList=new ArrayList<>(); empList.add(e1); empList.add(e2); empList.add(e3); for(Employee e:empList){ if(Employee.isJoinAfter2012(e.getJoiningDate())){ //Make a list of these Employees or //increment a counter to get total number of Employees joined after 2012 } }
It might look okay to you but It involves boilerplate code and it doesn’t convey the intent of the programmer. You can argue that above code is easy to read and does convey what programmer is trying to achieve. But it gets easily polluted when we have nested for loops with lots of if-else tests and local variables. Then it becomes really hard to figure out what code is trying to do.
Lets see how we can use Java 8 Streams for this task.
Suppose we have to find out the list of all employees joined after 2012. Below is the code using Stream API
List<Employee> after2012List=empList.stream() .filter(emp -> Employee.isJoinAfter2012(emp.getJoiningDate()) ) .collect(Collectors.toList());
Similarly if we have to just count the total number of employees joined after 2012 we can do it as below
long empAfter2012=empList.stream() .filter(emp->Employee.isJoinAfter2012(emp.getJoiningDate()) ) .count();
Note that while using Stream API we are just telling the language what we want, not how to do it. Like in first example we just created a stream from the list then used a filter to keep only those employees who joined after 2012 and then returned the list of those employees. We didn't do any if tests, we did not created an ArrayList and added each employee to that list manually.
Similarly in second example we just passed the filtering criteria and then we returned the total count of all the employees who pass that criteria. Note that we did not maintain any local counter and we don't have to do manual increment to it. With Streams you just tell what you need , you are not required to tell how to do it.
Java 8 Stream API is very powerful and it really makes a complex task very easy. If you are still not convinced with the power of Streams just hold on after going through below examples I am sure you will be able to appreciate it.
I downloaded the movie dataset containing information about 3452 movies from
Zepfanman IMDB Movies Data Sheet
Lets extract useful information from it using Streams. I have cleaned the downloaded dataset a bit by deleting unwanted information and made it as a semi-colon(;) separated values.
The dataset groups the information of each movie in 14 columns
Title : Name of the Movie
Title Type : Type of Movie (i.e. documentary or feature film or short film etc.)
Directors : Name of movie directors of that movie
AVG Rating : Average rating of the movie
IMDB Rating : Rating of movie on IMDB
Rotten Tomato Rating : Rating of movie on Rotten Tomato
Runtime : Duration of movie in minutes
Year : Year in which movie was released
Genres : Genres of the movie like action, biography, drama etc.
Number of IMDB Votes : Votes it got on IMDB from users
IMDB Top 250 : Whether a movie is under IMDB top 250 list ( if yes then Y otherwise N or blank)
1001 Must See : Whether a movie is under 1001 Must see list (if yes then Y otherwise N or blank)
Desire to see (A-F grading) : Grade on scale A to F reflecting the popularity of that movie
URL : URL where you can see the movie on IMDB
Note : Not all the columns have a value for each movie some of the columns are blank if there is no data for those columns. Like for second movie in the dataset (movie 24) directors name and many other columns are blank.
How to create a Stream from a File ?
To use the Stream API we have to first get a stream from the dataset file movies.csv
Java 8 provides a public java.util.stream.Stream<java.lang.String> lines() in the BufferedReader class to easily get a Stream from a file.
BufferedReader breader=null; try{ Path path = Paths.get("src/resources", "movies.csv"); breader = Files.newBufferedReader(path, StandardCharsets.ISO_8859_1); }catch(IOException exception){ System.out.println("Error occurred while trying to read the file"); System.exit(0); } List<String> lines=breader.lines() .collect(Collectors.toList());
Above code reads the movies.csv file, breader.lines() returns a Stream then we call collect() to return a list of lines from the file. Now we are ready to play with Stream API.
Extract names of all the movies and save it in a list
List<String> movies=lines.stream() .skip(1) .map(line -> Arrays.asList(line.split(";"))) .map(list -> {String movie=list.get(0).trim(); return movie;}) .collect(Collectors.toList()); System.out.println(movies);
First we get a Stream by calling stream() on the list we created before by reading the file, we skipped first line of stream as it is header line having column names. Then we split the stream(line) with ; as delimiter which returns a String array. We converted the String array into a list. Since we need only name of the movies so we created a stream of only movie names using map() again.
We use map() to convert a stream of one thing into a stream of another thing. In the end we collected all the movie names into a list using Collectors.toList()
Rather then using two map() consecutively, we can achieve the same thing with just one map().
Rather then using two map() consecutively, we can achieve the same thing with just one map().
Below code is equivalent to the above one
List<String> movieNames=lines.stream() .skip(1) .map(line -> Arrays.asList(line.split(";")).get(0).trim()) .collect(Collectors.toList()); System.out.println(movieNames);
In above code snippet map(line -> Arrays.asList(line.split(";")).get(0)).trim() passes the stream of only movie names to collect()
Lets find out the name of the director who directed movie Red River
lines.stream() .skip(1) .map(line -> Arrays.asList(line.split(";"))) .filter(movie -> {String movieName=movie.get(0); return movieName.trim().equalsIgnoreCase("Red River");}) .forEach(list -> {String director=list.get(2); System.out.println("Red River was directed by "+director);});
This time we use the filter() to check whether movie name is Red River or not.If it is, we retrieve its director to get the director of movie Red River.Note that forEach() will be executed only when filter condition returns true.
List the names of all the Documentary movies from the dataset
List<String> docMovies=lines.stream() .skip(1) .map(line -> Arrays.asList(line.split(";"))) .filter(list -> {String type=list.get(1); return type.trim().equalsIgnoreCase("documentary");}) .map(movie -> {String movieName=movie.get(0); return movieName;}) .collect(Collectors.toList()); System.out.println(docMovies);
Note how we use map() after filter() to just get the names of all documentary movies. That's the beauty of stream, you can manipulate it by chaining methods one after another in the way you want.
Find out total number of Documentary movies from the dataset
Since this time we are only interested in the total count of documentary movies we can use count() from the Stream class to get that.
long totalDocMovies=lines.stream() .skip(1) .map(line -> Arrays.asList(line.split(";")).get(1)) .filter(movieType -> movieType.trim().equalsIgnoreCase("documentary")) .count(); System.out.println("Total Documentary Movies : "+totalDocMovies);
map(line -> Arrays.asList(line.split(";")).get(1)) passes the Stream of only movie types to filter.
List of all the documentary movies released in 2000
List<String> doc2000List=lines.stream() .skip(1) .map(line -> Arrays.asList(line.split(";"))) .filter(movie -> {String movieType=movie.get(1).trim(); return (!movieType.equals("")&& movieType.equalsIgnoreCase("documentary"));}) .filter(list -> {String year=list.get(7).trim(); return (!year.equals("")&& year.equals("2000"));}) .map(movie -> {String name=movie.get(0); return name;}) .collect(Collectors.toList()); System.out.println(doc2000List);
Note that we used two filters, first one to filter documentary movies and second one to filter documentary movies released in 2000. At the end we collected the names of these movies.
Rather than using two filter() we can do both test in just one filter() as shown here
List<String> doc2000=lines.stream() .skip(1) .map(line -> Arrays.asList(line.split(";"))) .filter(movie -> { String type=movie.get(1).trim(); String year=movie.get(7).trim(); return (!type.equals("")&& !year.equals("")&& type.equalsIgnoreCase("documentary")&& year.equals("2000"));}) .map(movie -> {String name=movie.get(0); return name;}) .collect(Collectors.toList()); System.out.println(doc2000);
List of documentary movies released in 2000 having IMDB rating greater than or equal to 7
This is similar to as above, we just required to add another test to look for IMDB rating of the movie
List<String> doc2000IMDB7=lines.stream() .skip(1) .map(line -> Arrays.asList(line.split(";"))) .filter(movie -> {String type=movie.get(1).trim(); return (!type.equals("")&& type.equalsIgnoreCase("documentary"));}) .filter(movie -> {String year=movie.get(7).trim(); return (!year.equals("")&& year.equals("2000"));}) .filter(movie -> {String imdb=movie.get(4).trim(); return (!imdb.equals("")&& Float.parseFloat(imdb)>= 7);}) .map(movie -> {String movieName=movie.get(0).trim(); return movieName;}) .collect(Collectors.toList()); System.out.println(doc2000IMDB7);
If you want you can use just one filter() and put all those three tests in it.
Find out the minimum runtime of a movie from the dataset, In other words what is the minimum duration of any movie among all the movies
String minimumRuntime=lines.stream() .skip(1) .map(line -> {String runtime=line.split(";")[6]; return runtime.trim();}) .filter(movieRuntime -> !movieRuntime.equals("")) .min(Comparator.comparing(time -> Float.parseFloat(time))) .get(); System.out.println("Minimum Runtime "+minimumRuntime);
We used map() to get only runtime of the movies from the stream. Then we filtered only those movies for which runtime is not empty. If we don’t filter we might get NumberFormatException trying to parse an empty string to float. Now we can use min() to find out the minimum runtime among all.
Find out the maximum runtime of a movie from the dataset, In other words what is the maximum duration of any movie among all the movies
java.util.stream.Stream class also have a max() which we will use to find out the maximum runtime
String maximumRuntime=lines.stream() .skip(1) .map(line -> {String runtime=line.split(";")[6]; return runtime.trim();}) .filter(movieRuntime -> !movieRuntime.equals("")) .max(Comparator.comparing(time -> Float.parseFloat(time))) .get(); System.out.println("Maximum Runtime "+maximumRuntime);
List down the Top 5 voted movies on IMDB
lines.stream() .skip(1) .map(line -> Arrays.asList(line.split(";")) ) .filter(movie -> {String imdbVotes=movie.get(9).trim(); return !imdbVotes.equals("");}) .sorted((movie1,movie2) -> {String m1Votes=movie1.get(9).trim(); String m2Votes=movie2.get(9).trim(); return Integer.valueOf(m2Votes).compareTo(Integer.valueOf(m1Votes));} ) .limit(5) .forEach(movie -> {System.out.println(movie.get(0)+" --- "+movie.get(9));});
There is lot more in new Stream API than what we have covered here. But this will help you get started writing code using Java 8 Stream API
If you are interested in trying the code snippets yourself get it from Github
Stream (computing)
Java (programming language)
API
Opinions expressed by DZone contributors are their own.
Trending
-
Extending Java APIs: Add Missing Features Without the Hassle
-
Microservices Decoded: Unraveling the Benefits, Challenges, and Best Practices for APIs
-
TDD vs. BDD: Choosing The Suitable Framework
-
Design Patterns for Microservices: Ambassador, Anti-Corruption Layer, and Backends for Frontends
Comments