Java 8 Creating, Combining and Sorting Streams
Introduction
This tutorial covers three everyday stream tasks in Java 8 that the other tutorials in the series touch only in passing: creating a stream from different sources, combining several streams into one, and sorting a stream. We will start with an overview of the ways to create a stream - from values, arrays, strings, regular expressions and builders. We will then compareStream.concat() with flatMap() for joining streams, and look at sorted() with and without a ComparatorClick to read: Comparator, including why it behaves differently from most other operations. Lastly, we will run one example covering all of these and list the common pitfalls.(For the basics of streams, see the Java 8 Streams API tutorialClick to read: Java 8 Streams API tutorial; infinite streams have their own iterate and generate tutorialClick to read: iterate and generate tutorial.)
Ways to create a stream
Stream.of(T... values): a stream of the given values.Stream.empty()gives a stream with none.Arrays.stream(array)andArrays.stream(array, from, to): a stream over an array, or a part of it. Forint[],long[]anddouble[]it returns a primitive stream.collection.stream(): the most common source - see also parallelStream()Click to read tutorial: Java 8 Parallel Streams Tutorial with Examples."text".chars(): anIntStreamof the characters of a string.Pattern.splitAsStream(text): splits text around a regular expression, lazily - new in Java 8.Stream.builder(): add elements one at a time, thenbuild(), when the elements are produced by code rather than held in a collection.Stream.iterate()andStream.generate(): infinite streams;IntStream.range(): number ranges (see range and rangeClosedClick to read: range and rangeClosed).Files.lines(path)andBufferedReader.lines(): the lines of a file - see the word count exampleClick to read: word count example.
Combining streams - concat() or flatMap()
Definition ofStream.concat() in java.util.stream.Stream -static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)- The result contains all elements of
afollowed by all elements ofb, lazily - nothing is copied up front. - It takes exactly two streams. The JDK documentation warns that building a long chain of nested
concat()calls can cause deep call stacks and even aStackOverflowError. - For any number of streams or collections, put them in a stream and flatten it with flatMap()Click to read: flatMap():
Stream.of(a, b, c).flatMap(Function.identity()), orlists.stream().flatMap(List::stream).
Sorting - sorted() and sorted(Comparator)
sorted() sorts by natural order - the elements must implement Comparable, or the terminal operation throws a ClassCastException. sorted(Comparator) sorts with the given comparator, which can be built with Comparator.comparing(), reversed and chained with thenComparing(). For ordered streams the sort is stable: equal elements keep their original order.Unlike
filter() or map(), which pass each element on as soon as they have processed it, sorted() is a stateful operation: it cannot emit the smallest element until it has seen the last one.
Java 8 code example of creating, combining and sorting streams
The example uses a smallEmployee class with a name and a salary.
Employee.java
//Employee.java
public class Employee {
private final String name;
private final double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() { return name; }
public double getSalary() { return salary; }
@Override
public String toString() { return name + " (" + salary + ")"; }
}
Java 8 code showing stream sources, Stream.concat, flatMap and sorted
//StreamSources.java
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class StreamSources {
public static void main(String[] args) {
// 1. Creating streams from values, arrays, strings and builders
Stream<String> fromValues = Stream.of("Tom", "Harry", "Nancy");
String[] departments = {"Finance", "Sales", "IT", "HR"};
Stream<String> fromArrayPart = Arrays.stream(departments, 1, 3); // elements 1 and 2
Stream<String> fromPattern = Pattern.compile(",\\s*").splitAsStream("Ethan, Deborah,Tom");
IntStream fromChars = "JAVA".chars();
Stream<String> built = Stream.<String>builder().add("first").add("second").build();
System.out.println("1. of: " + fromValues.collect(Collectors.toList())
+ ", array part: " + fromArrayPart.collect(Collectors.toList())
+ ", splitAsStream: " + fromPattern.collect(Collectors.toList()));
System.out.println(" chars: " + fromChars.mapToObj(c -> String.valueOf((char) c))
.collect(Collectors.joining("-"))
+ ", builder: " + built.collect(Collectors.toList())
+ ", empty: " + Stream.empty().count() + " elements");
// 2. Combining two streams with concat
Stream<String> finance = Stream.of("Tom Jones", "Harry Major");
Stream<String> sales = Stream.of("Ethan Hardy", "Nancy Smith");
List<String> both = Stream.concat(finance, sales).collect(Collectors.toList());
System.out.println("2. concat: " + both);
// 3. Combining many streams with flatMap
List<List<String>> teams = Arrays.asList(
Arrays.asList("Tom Jones", "Harry Major"),
Arrays.asList("Ethan Hardy"),
Arrays.asList("Nancy Smith", "Deborah Sprightly"));
List<String> everyone = teams.stream()
.flatMap(List::stream)
.collect(Collectors.toList());
System.out.println("3. flatMap of 3 teams: " + everyone);
// 4. Sorting: natural order, a Comparator, reversed and multi-level
List<Employee> staff = Arrays.asList(new Employee("Tom Jones", 5200),
new Employee("Harry Major", 4100), new Employee("Nancy Smith", 5200),
new Employee("Ethan Hardy", 6300));
System.out.println("4. names sorted: "
+ staff.stream().map(Employee::getName).sorted().collect(Collectors.toList()));
System.out.println(" by salary, highest first, then name: " + staff.stream()
.sorted(Comparator.comparingDouble(Employee::getSalary).reversed()
.thenComparing(Employee::getName))
.map(Employee::getName)
.collect(Collectors.toList()));
// 5. sorted() is stateful: it must see every element before passing one on
StringBuilder trace = new StringBuilder();
Stream.of(3, 1, 2)
.peek(n -> trace.append("in:").append(n).append(' '))
.sorted()
.forEach(n -> trace.append("out:").append(n).append(' '));
System.out.println("5. " + trace.toString().trim());
}
}
1. of: [Tom, Harry, Nancy], array part: [Sales, IT], splitAsStream: [Ethan, Deborah, Tom] chars: J-A-V-A, builder: [first, second], empty: 0 elements 2. concat: [Tom Jones, Harry Major, Ethan Hardy, Nancy Smith] 3. flatMap of 3 teams: [Tom Jones, Harry Major, Ethan Hardy, Nancy Smith, Deborah Sprightly] 4. names sorted: [Ethan Hardy, Harry Major, Nancy Smith, Tom Jones] by salary, highest first, then name: [Ethan Hardy, Nancy Smith, Tom Jones, Harry Major] 5. in:3 in:1 in:2 out:1 out:2 out:3
Explanation of the code
- Part 1:
Arrays.stream(departments, 1, 3)takes the elements at index 1 and 2 (the end index is exclusive).splitAsStream()splits on a comma followed by any spaces.chars()givesintcode points, turned back into strings withmapToObj().Stream.<String>builder()needs the type witness because nothing else tells the compiler the element type. - Part 2:
concat()joins the Finance stream and the Sales stream, keeping their order. Each input stream is consumed, so neither can be used again. - Part 3: three team lists are flattened into one list of five names with
flatMap(List::stream)- the way to combine any number of sources. - Part 4:
sorted()on the names usesString's natural order. The second pipeline sorts by salary, highest first, and breaks the tie between Nancy and Tom (both 5200) by name. - Part 5: the trace shows
sorted()as a barrier: all three elements passpeek()("in") before any reachesforEach()("out"), and they come out in sorted order.
Common pitfalls
- Sorting an infinite stream:
Stream.iterate(1, n -> n + 1).sorted().limit(5)never finishes, becausesorted()waits for an end that never comes. Putlimit()beforesorted(). - Sorting what is not
Comparable:sorted()on objects without a natural order compiles but throwsClassCastExceptionwhen the stream runs. Pass aComparator. - Reusing a stream: a stream can be consumed only once. After
concat(a, b), bothaandbare used up; create new streams if you need them again. Arrays.asList(intArray).stream(): for anint[], this is a stream of one element - the array itself. UseArrays.stream(intArray)orIntStream.of().- Losing the order after sorting: collecting a sorted stream into a
HashSetorHashMapdiscards the order - see how to sort a mapClick to read tutorial: Java 8 - How to Sort a Map by Key or Value.
Summary
In this tutorial we first listed the ways to create a stream in Java 8:Stream.of(), Arrays.stream(), collections, chars(), splitAsStream(), builders, generators and files. Next we compared Stream.concat(), for exactly two streams, with flatMap(), for any number. We then saw sorted() and sorted(Comparator), and why sorting is a stateful barrier. We then ran an example of all of these, including a trace of sorted(). Lastly, we listed the common pitfalls, from sorting infinite streams to reusing consumed ones.Java 8 Tutorials
Java 8 ConcurrentHashMap, LongAdder and StampedLockClick to read tutorial: Java 8 ConcurrentHashMap, LongAdder and StampedLock Java 8 Base64 Encoding and StringJoiner TutorialClick to read tutorial: Java 8 Base64 Encoding and StringJoiner Tutorial Java 8 CompletableFuture Tutorial with ExamplesClick to read tutorial: Java 8 CompletableFuture Tutorial with Examples Java 8 CompletableFuture: Combining and Error HandlingClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling Java 8 Parallel Streams Tutorial with ExamplesClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples Java 8 Collectors.toMap Tutorial with ExamplesClick to read tutorial: Java 8 Collectors.toMap Tutorial with Examples Java 8 Custom Collector with Collector.of() TutorialClick to read tutorial: Java 8 Custom Collector with Collector.of() Tutorial
Java 8 ConcurrentHashMap, LongAdder and StampedLockClick to read tutorial: Java 8 ConcurrentHashMap, LongAdder and StampedLock Java 8 Base64 Encoding and StringJoiner TutorialClick to read tutorial: Java 8 Base64 Encoding and StringJoiner Tutorial Java 8 CompletableFuture Tutorial with ExamplesClick to read tutorial: Java 8 CompletableFuture Tutorial with Examples Java 8 CompletableFuture: Combining and Error HandlingClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling Java 8 Parallel Streams Tutorial with ExamplesClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples Java 8 Collectors.toMap Tutorial with ExamplesClick to read tutorial: Java 8 Collectors.toMap Tutorial with Examples Java 8 Custom Collector with Collector.of() TutorialClick to read tutorial: Java 8 Custom Collector with Collector.of() Tutorial