What's New in Java 8: Streams
The Stream interface is such a fundamental part of Java 8 it deserves its own chapter.
1. What is a Stream?
The Stream interface is located in the java.util.stream package. It represents a sequence of objects somewhat like the Iterator interface. However, unlike the Iterator, it supports parallel execution.
The Stream interface supports the map/filter/reduce pattern and executes lazily, forming the basis (along with lambdas) for functional-style programming in Java 8.
There are also corresponding primitive streams (IntStream, DoubleStream, and LongStream) for performance reasons.
2. Generating Streams
There are many ways to create a Stream in Java 8. Many of the existing Java core library classes have Stream returning methods in Java 8.
Streaming Collections
The most obvious way to create a stream is from a Collection.
The Collection interface has two default methods on it for creating streams:
stream(): Returns a sequential Stream with the collection as its source.
parallelStream(): Returns a possibly parallel Stream with the collection as its source.
The ordering of the Stream relies on the underlying collection just like an Iterator.
Streaming Files
The BufferedReader now has the lines() method which returns a Stream; for example3:
1 try (FileReader fr = new FileReader("file");
2 BufferedReader br = new BufferedReader(fr)) {
3 br.lines().forEach(System.out::println);
4 }
You can also read a file as a Stream using Files.lines(Path filePath); for example:
1 try (Stream st = Files.lines(Paths.get("file"))) {
2 st.forEach(System.out::println);
3 }
Note this populates lazily; it does not read the entire file when you call it.
warning
Files.lines(Path): Any IOException that is thrown while processing the file (after the file is opened) will get wrapped in an UncheckedIOException and thrown.
Streaming File Trees
There are several static methods on the Files class for navigating file trees using a Stream.
list(Path dir) – Stream of files in the given directory.
walk(Path dir)4 – Stream that traverses the file tree depth-first starting at the given directory.
walk(Path dir, int maxDepth) – Same as walk(dir) but with a maximum depth.
Streaming Text Patterns
The Pattern class now has a method, splitAsStream(CharSequence), which creates a Stream.
For example:
1 import java.util.regex.Pattern;
2 // later on...
3 Pattern patt = Pattern.compile(",");
4 patt.splitAsStream("a,b,c")
5 .forEach(System.out::println);
The above uses a very simple pattern, a comma, and splits the text into a stream and prints it out. This would produce the following output:
1 a
2 b
3 c
Infinite Streams
Using the generate or iterate static methods on Stream, you can create a Stream of values including never ending streams. For example, you could call generate in the following way to create an infinite supply of objects:
1 Stream.generate(() -> new Dragon());
For example, you could use this technique to produce a stream of CPU load or memory usage. However, you should use this with caution. It is similar to an infinite loop.
You could also use generate to create an infinite random number supply; for example:
1 Stream.generate(() -> Math.random());
However, the java.util.Random class does this for you with the following new methods: ints(), longs(), and doubles(). Each of those methods is overloaded with definitions similar to the following:
ints(): An infinite Stream of random integers.
ints(int n, int m): An infinite Stream of random integers from n (inclusive) to m (exclusive).
ints(long size): A Stream of given size of random integers.
ints(long size, int n, int m): A Stream of given size of random integers with given bounds.
The iterate method is similar to generate except it takes an initial value and a Function that modifies that value. For example, you can iterate over the Integers using the following code:
1 Stream.iterate(1, i -> i+1)
2 .forEach(System.out::print);
This would print out “1234…” continuously until you stop the program.
tip
There are ways to limit an infinite stream which we will cover later (filter and limit).
Ranges
There are also new methods for creating ranges of numbers as Streams.
For example, the static method, range, on the IntStream interface:
1 IntStream.range(1, 11)
2 .forEach(System.out::println);
The above would print out the numbers one through ten.
Each primitive Stream (IntStream, DoubleStream, and LongStream) has a corresponding range method.
Streaming Anything
You can create a Stream from any number of elements or an array using the two following methods:
1 Stream<Integer> s = Stream.of(1, 2, 3);
2 Stream<Object> s2 = Arrays.stream(array);
Stream.of can take any number of parameters of any type.
3. For Each
The most basic thing you can do with a Stream is loop through it using the forEach method.
For example, to print out all of the files in the current directory, you could do the following:
1 Files.list(Paths.get("."))
2 .forEach(System.out::println);
For the most part, this replaces the “for loop”. It is more concise, and more object-oriented since you are delegating the implementation of the actual loop.
4. Map/Filter/Reduce
Lambda expressions and default methods allow us to implement map/filter/reduce in Java 8. Actually it is already implemented for us in the standard library.
For example, imagine you want to get the current point scores from a list of player-names and find the player with the most points. You have a simple class, PlayerPoints, and a getPoints method defined as the following:
1 public static class PlayerPoints {
2 public final String name;
3 public final long points;
4
5 public PlayerPoints(String name, long points) {
6 this.name = name;
7 this.points = points;
8 }
9
10 public String toString() {
11 return name + ":" + points;
12 }
13 }
14
15 public static long getPoints(final String name) {
16 // gets the Points for the Player
17 }
Finding the highest player could be done very simply in Java 8 as shown in the following code:
1 PlayerPoints highestPlayer =
2 names.stream().map(name -> new PlayerPoints(name, getPoints(name)))
3 .reduce(new PlayerPoints("", 0.0),
4 (s1, s2) -> (s1.points > s2.points) ? s1 : s2);
This could also be done in Java 7 with the dollar library (or similarly with Guava or Functional-Java), but it would be much more verbose as shown in the following:
1 PlayerPoints highestPlayer =
2 $(names).map(new Function<String, PlayerPoints>() {
3 public PlayerPoints call(String name) {
4 return new PlayerPoints(name, getPoints(name));
5 }
6 })
7 .reduce(new PlayerPoints("", 0.0),
8 new BiFunction<PlayerPoints, PlayerPoints, PlayerPoints>() {
9 public PlayerPoints call(PlayerPoints s1, PlayerPoints s2) {
10 return (s1.points > s2.points) ? s1 : s2;
11 }
12 });
The major benefit to coding this way (apart from the reduction in lines of code) is the ability to hide the underlying implementation of map/reduce. For example, it’s possible that map and reduce are implemented concurrently, allowing you to easily take advantage of multiple processors. We’ll describe one way to do this (ParallelArray) in the following section.
5. Parallel Array
The ParallelArray was part of JSR-166, but ended up being excluded from the standard Java lib. It does exist and was released to the public domain (you can download it from the JSR website).
Although it was already out there, it really wasn’t easy to use until closures were included in the Java language. In Java 7 using the ParallelArray looks like the following:
1 // with this class
2 public class Student {
3 String name;
4 int graduationYear;
5 double gpa;
6 }
7 // this predicate
8 final Ops.Predicate<Student> isSenior =
9 new Ops.Predicate<>() {
10 public boolean op(Student s) {
11 return s.graduati Student.THIS_YEAR;
12 }
13 };
14 // and this conversion operation
15 final Ops.ObjectToDouble<Student> selectGpa =
16 new Ops.ObjectToDouble<>() {
17 public double op(Student student) {
18 return student.gpa;
19 }
20 };
21 // create a fork-join-pool
22 ForkJoinPool fjPool = new ForkJoinPool();
23 ParallelArray<Student> students = new ParallelArray<>(fjPool, data);
24 // find the best GPA:
25 double bestGpa = students.withFilter(isSenior)
26 .withMapping(selectGpa)
27 .max();
In Java 8, you can do the following:
1 // create a fork-join-pool
2 ForkJoinPool pool = new ForkJoinPool();
3 ParallelArray<Student> students = new ParallelArray<>(pool,data);
4 // find the best GPA:
5 double bestGpa = students
6 .withFilter((Student s) -> (s.graduati THIS_YEAR))
7 .withMapping((Student s) -> s.gpa)
8 .max();
However, Java 8’s addition of stream() and parallelStream() make this even easier:
1 double bestGpa = students
2 .parallelStream()
3 .filter(s -> (s.graduati THIS_YEAR))
4 .mapToDouble(s -> s.gpa)
5 .max().getAsDouble();
This makes it extremely simple to switch between a sequential implementation and a concurrent one.
Groovy GPars
You can do something similar to this right now if you use Groovy with the GPars library in the following way:
1 GParsPool.withPool {
2 // a map-reduce functional style (students is a Collection)
3 def bestGpa = students.parallel
4 .filter{ s -> s.graduati Student.THIS_YEAR }
5 .map{ s -> s.gpa }
6 .max()
7 }
The static method GParsPool.withPool takes in a closure and augments any Collection with several methods (using Groovy’s Category mechanism). The parallel method actually creates a ParallelArray (JSR-166) from the given Collection and uses it with a thin wrapper around it.
6. Peek
You can peek into a stream to do some action without interrupting the stream.
For example you could print out elements to debug code:
1 Files.list(Paths.get("."))
2 .map(Path::getFileName)
3 .peek(System.out::println)
4 .forEach(p -> doSomething(p));
You can use any action you want, but you should not try to modify elements; you should use map instead.
7. Limit
The limit(int n) method can be used to limit a stream to the given number of elements. For example:
1 Random rnd = new Random();
2 rnd.ints().limit(10)
3 .forEach(System.out::println);
The above would print out ten random integers.
8. Sort
Stream also has the sorted() method for sorting a stream. Like all intermediate methods on Stream (such as map, filter, and peek), the sorted() method executes lazily. Nothing happens until a terminating operation (such as reduce or forEach) is called. However, you should call a limiting operation like limit before calling sorted() on an infinite stream.
For example, the following would throw a runtime exception (using build 1.8.0-b132):
1 rnd.ints().sorted().limit(10)
2 .forEach(System.out::println);
However, the following code works just fine:
1 rnd.ints().limit(10).sorted()
2 .forEach(System.out::println);
Also, you should call sorted() after any calls to filter. For example, this code prints out the first five Java file-names in the current directory:
1 Files.list(Paths.get("."))
2 .map(Path::getFileName) // still a path
3 .map(Path::toString) // convert to Strings
4 .filter(name -> name.endsWith(".java"))
5 .sorted() // sort them alphabetically
6 .limit(5) // first 5
7 .forEach(System.out::println);
The code above does the following:
Lists the files in the current directory.
Maps those files to file names.
Finds names that end with “.java”.
Takes only the first five (sorted alphabetically).
Prints them out.
9. Collectors and Statistics
Since Streams are lazily evaluated and support parallel execution, you need a special way to combine results; this is called a Collector.
A Collector represents a way to combine the elements of a Stream into one result. It consists of three things:
A supplier of an initial value.
An accumulator which adds to the initial value.
A combiner which combines two results into one.
There are two ways to do this: collect(supplier,accumulator,combiner), or collect(Collector) (types left off for brevity).
Luckily, Java 8 comes with several Collectors built in. Import them the following way:
1 import static java.util.stream.Collectors.*;
Simple Collectors
The simplest collectors are things like toList() and toCollection():
1 // Accumulate names into a List
2 List<String> list = dragons.stream()
3 .map(Dragon::getName)
4 .collect(toList());
5
6 // Accumulate names into a TreeSet
7 Set<String> set = dragons.stream()
8 .map(Dragon::getName)
9 .collect(toCollection(TreeSet::new));
Joining
If you’re familiar with Apache Commons’ StringUtil.join, the joining collector is similar to it. It combines the stream using a given delimiter. For example:
1 String names = dragons.stream()
2 .map(Dragon::getName)
3 .collect(joining(","));
This would combine all of the names into one String separated by commas.
Statistics
More complex collectors resolve to a single value. For example, you can use an “averaging” Collector to get the average; for example:
1 System.out.println("\n----->Average line length:");
2 System.out.println(
3 Files.lines(Paths.get("Nio.java"))
4 .map(String::trim)
5 .filter(s -> !s.isEmpty())
6 .collect(averagingInt(String::length))
7 );
The above code calculates the average length of non-empty lines in the file “Nio.java”.
Sometimes you want to collect multiple statistics about a collection. Because Streams are consumed when you call collect, you need to calculate all of your statistics at once. This is where SummaryStatistics comes in. First import the one you want to use:
1 import java.util.IntSummaryStatistics;
Then use the summarizingInt collector; for example:
1 IntSummaryStatistics stats = Files.lines(Paths.get("Nio.java"))
2 .map(String::trim)
3 .filter(s -> !s.isEmpty())
4 .collect(summarizingInt(String::length));
5
6 System.out.println(stats.getAverage());
7 System.out.println("count=" + stats.getCount());
8 System.out.println("max=" + stats.getMax());
9 System.out.println("min=" + stats.getMin());
The above code performs the same average as before, but also computes the maximum, minimum, and count of the elements.
tip
There’s also summarizingLong and summarizingDouble.
Equivalently, you can map your stream to a primitive type and then call summaryStatistics(). For example:
1 IntSummaryStatistics stats = Files.lines(Paths.get("Nio.java"))
2 .map(String::trim)
3 .filter(s -> !s.isEmpty())
4 .mapToInt(String::length)
5 .summaryStatistics();
10. Grouping and Partitioning
The groupingBy collector groups elements based on a function you provide. For example:
1 // Group by first letter of name
2 List<Dragon> drag>
3 Map<Character,List<Dragon>> map = dragons.stream()
4 .collect(groupingBy(dragon -> dragon.getName().charAt(0)));
Similarly, the partitioningBy method creates a map with a boolean key. For example:
1 // Group by whether or not the dragon is green
2 Map<Boolean,List<Dragon>> map = dragons.stream()
3 .collect(partitioningBy(Dragon::isGreen));
tip
Parallel Grouping
To execute grouping in parallel (if you don’t care about ordering) you should use the groupingByConcurrent method. The underlying stream should be unordered to allow grouping to occur in parallel; for example: dragons.parallelStream().unordered().collect(groupingByConcurrent(Dragon::getColor));.
11. Comparisons to Java 7
To better illustrate the benefit of Streams in Java 8, here are some examples of code from Java 7 compared to their new versions.
Finding a maximum
1 // Java 7
2 double max = 0;
3
4 for (Double d : list) {
5 if (d > max) {
6 max = d;
7 }
8 }
9 //Java 8
10 max = list.stream().reduce(0.0, Math::max);
11 // or
12 max = list.stream().mapToDouble(Number::doubleValue).max().getAsDouble();
Calculating an average
1 double total = 0;
2 double ave = 0;
3 // Java 7
4 for (Double d : list) {
5 total += d;
6 }
7 ave = total / ((double) list.size());
8 //Java 8
9 ave = list.stream().mapToDouble(Number::doubleValue).average().getAsDouble();
Printing the numbers one through ten
1 // Java 7
2 for (int i = 1; i < 11; i++) {
3 System.out.println(i);
4 }
5 // Java 8
6 IntStream.range(1, 11)
7 .forEach(System.out::println);
8 //or
9 Stream.iterate(1, i -> i+1).limit(10)
10 .forEach(System.out::println);
Joining Strings
1 // Java 7 using commons-util
2 List<String> names = new LinkedList<>();
3 for (Dragon dragon : dragons)
4 names.add(dragon.getName());
5 String names = StringUtils.join(names, ",");
6 // Java 8
7 String names = dragons.stream()
8 .map(Dragon::getName)
9 .collect(Collectors.joining(","));
You must Sign up as a member of Effecthub to view the content.
A PHP Error was encountered
Severity: Notice
Message: Undefined index: HTTP_ACCEPT_LANGUAGE
Filename: helpers/time_helper.php
Line Number: 22
2312 views 0 comments
You must Sign up as a member of Effecthub to join the conversation.