| Start | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Using Streams A Example: (see it work on Ideone (opens new window)) Output: APPLE The operations performed by the above code can be summarized as follows:
**Note** that operations defined on the `Stream` are performed **because** of the terminal operation. Without a terminal operation, the stream is not processed. Streams can not be reused. Once a terminal operation is called, the `Stream` object becomes unusable. Note that operations defined on the Operations (as seen above) are chained together to form what can be seen as a query on the data. Closing StreamsNote that a The An example use case where a The The run handler will only execute if the Processing OrderA In a sequential mode, the elements are processed in the order of the source of the Example: Live on Ideone (opens new window) Parallel mode allows the use of multiple threads on multiple cores but there is no guarantee of the order in which elements are processed. If multiple methods are called on a sequential Example: Live on Ideone (opens new window) Differences from Containers (or Collections (opens new window))While some actions can be performed on both Containers and Streams, they ultimately serve different purposes and support different operations. Containers are more focused on how the elements are stored and how those elements can be accessed efficiently. A |
||||||||||||||||||||
| Consuming Streams A In the following example, no terminal operation is added to the Live on Ideone (opens new window) This is a You could also use Live on Ideone (opens new window) Output: 2 After the terminal operation is performed, the Although a given stream object cannot be reused, it's easy to create a reusable Output: foo This works because In general, a |
||||||||||||||||||||
| Creating a Frequency Map The A classic example of this principle is to use a The downstream operation is itself a collector ( This would produce the following output: banana=1 |
||||||||||||||||||||
| Infinite Streams It is possible to generate a This example generates a Output: 1 Another way of generating an infinite stream is using the Stream.generate (opens new window) method. This method takes a lambda (opens new window) of type Supplier (opens new window). |
||||||||||||||||||||
Collect Elements of a Stream into a Collection Collect with |
| Goal | Code |
|---|---|
Collect to a List | Collectors.toList() |
Collect to an ArrayList with pre-allocated size | Collectors.toCollection(() -> new ArrayList<>(size)) |
Collect to a Set | Collectors.toSet() |
Collect to a Set with better iteration performance | Collectors.toCollection(() -> new LinkedHashSet<>()) |
Collect to a case-insensitive Set<String> | Collectors.toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER)) |
Collect to an EnumSet<AnEnum> (best performance for enums) | Collectors.toCollection(() -> EnumSet.noneOf(AnEnum.class)) |
Collect to a Map<K,V> with unique keys | Collectors.toMap(keyFunc,valFunc) |
| Map MyObject.getter() to unique MyObject | Collectors.toMap(MyObject::getter, Function.identity()) |
| Map MyObject.getter() to multiple MyObjects | Collectors.groupingBy(MyObject::getter) |
Streams, and especially IntStreams, are an elegant way of implementing summation terms (∑). The ranges of the Stream can be used as the bounds of the summation.
E.g., Madhava's approximation of Pi is given by the formula (Source: wikipedia (opens new window)):
(opens new window)
This can be calculated with an arbitrary precision. E.g., for 101 terms:
double pi = Math.sqrt(12) *
IntStream.rangeClosed(0, 100)
.mapToDouble(k -> Math.pow(-3, -1 * k) / (2 * k + 1))
.sum();
Note: With double's precision, selecting an upper bound of 29 is sufficient to get a result that's indistinguishable from Math.Pi.
Note: Before deciding which Stream to use please have a look at ParallelStream vs Sequential Stream behavior (opens new window).
When you want to perform Stream operations concurrently, you could use either of these ways.
List<String> data = Arrays.asList("One", "Two", "Three", "Four", "Five");
Stream<String> aParallelStream = data.stream().parallel();
Or:
Stream<String> aParallelStream = data.parallelStream();
To execute the operations defined for the parallel stream, call a terminal operator:
aParallelStream.forEach(System.out::println);
(A possible) output from the parallel Stream:
Three
Four
One
Two
Five
The order might change as all the elements are processed in parallel (Which may make it faster). Use parallelStream (opens new window) when ordering does not matter.
In case networking is involved, parallel Streams may degrade the overall performance of an application because all parallel Streams use a common fork-join thread pool for the network.
On the other hand, parallel Streams may significantly improve performance in many other cases, depending of the number of available cores in the running CPU at the moment.
A Stream of items that are in turn streamable can be flattened into a single continuous Stream:
Array of List of Items can be converted into a single List.
List<String> list1 = Arrays.asList("one", "two");
List<String> list2 = Arrays.asList("three","four","five");
List<String> list3 = Arrays.asList("six");
List<String> finalList = Stream.of(list1, list2, list3).flatMap(Collection::stream).collect(Collectors.toList());
System.out.println(finalList);
// [one, two, three, four, five, six]
Map containing List of Items as values can be Flattened to a Combined List
Map<String, List<Integer>> map = new LinkedHashMap<>();
map.put("a", Arrays.asList(1, 2, 3));
map.put("b", Arrays.asList(4, 5, 6));
List<Integer> allValues = map.values() // Collection<List<Integer>>
.stream() // Stream<List<Integer>>
.flatMap(List::stream) // Stream<Integer>
.collect(Collectors.toList());
System.out.println(allValues);
// [1, 2, 3, 4, 5, 6]
List of Map can be flattened into a single continuous Stream
List<Map<String, String>> list = new ArrayList<>();
Map<String,String> map1 = new HashMap();
map1.put("1", "one");
map1.put("2", "two");
Map<String,String> map2 = new HashMap();
map2.put("3", "three");
map2.put("4", "four");
list.add(map1);
list.add(map2);
Set<String> output= list.stream() // Stream<Map<String, String>>
.map(Map::values) // Stream<List<String>>
.flatMap(Collection::stream) // Stream<String>
.collect(Collectors.toSet()); //Set<String>
// [one, two, three,four]
All java Collection<E>s have stream() (opens new window) and parallelStream() (opens new window) methods from which a Stream<E> can be constructed:
Collection<String> stringList = new ArrayList<>();
Stream<String> stringStream = stringList.parallelStream();
A Stream<E> can be created from an array using one of two methods:
String[] values = { "aaa", "bbbb", "ddd", "cccc" };
Stream<String> stringStream = Arrays.stream(values);
Stream<String> stringStreamAlternative = Stream.of(values);
The difference between Arrays.stream() (opens new window) and Stream.of() (opens new window) is that Stream.of() has a varargs parameter, so it can be used like:
Stream<Integer> integerStream = Stream.of(1, 2, 3);
There are also primitive Streams that you can use. For example:
IntStream intStream = IntStream.of(1, 2, 3);
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0);
These primitive streams can also be constructed using the Arrays.stream() method:
IntStream intStream = Arrays.stream(new int[]{ 1, 2, 3 });
It is possible to create a Stream from an array with a specified range.
int[] values= new int[]{1, 2, 3, 4, 5};
IntStream intStram = Arrays.stream(values, 1, 3);
Note that any primitive stream can be converted to boxed type stream using the boxed method :
Stream<Integer> integerStream = intStream.boxed();
This can be useful in some case if you want to collect the data since primitive stream does not have any collect method that takes a Collector as argument.
Reusing intermediate operations of a stream chain
Stream is closed when ever terminal operation is called. Reusing the stream of intermediate operations, when only terminal operation is only varying. we could create a stream supplier to construct a new stream with all intermediate operations already set up.
Supplier<Stream<String>> streamSupplier = () -> Stream.of("apple", "banana","orange", "grapes", "melon","blueberry","blackberry")
.map(String::toUpperCase).sorted();
streamSupplier.get().filter(s -> s.startsWith("A")).forEach(System.out::println);
// APPLE
streamSupplier.get().filter(s -> s.startsWith("B")).forEach(System.out::println);
// BANANA
// BLACKBERRY
// BLUEBERRY
int[] arrays can be converted to List<Integer> using streams
int[] ints = {1,2,3};
List<Integer> list = IntStream.of(ints).boxed().collect(Collectors.toList());
Java 8 provides classes called IntSummaryStatistics (opens new window), DoubleSummaryStatistics (opens new window) and LongSummaryStatistics (opens new window) which give a state object for collecting statistics such as count, min, max, sum, and average.
List<Integer> naturalNumbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics stats = naturalNumbers.stream()
.mapToInt((x) -> x)
.summaryStatistics();
System.out.println(stats);
Which will result in:
IntSummaryStatistics{count=10, sum=55, min=1, max=10, average=5.500000}
Streams of elements usually do not allow access to the index value of the current item. To iterate over an array or ArrayList while having access to indexes, use IntStream.range(start, endExclusive).
String[] names = { "Jon", "Darin", "Bauke", "Hans", "Marc" };
IntStream.range(0, names.length)
.mapToObj(i -> String.format("#%d %s", i + 1, names[i]))
.forEach(System.out::println);
The range(start, endExclusive) (opens new window) method returns another ÌntStream (opens new window) and the mapToObj(mapper) (opens new window) returns a stream of String.
Output:
#1 Jon
#2 Darin
#3 Bauke
#4 Hans
#5 Marc
This is very similar to using a normal for loop with a counter, but with the benefit of pipelining and parallelization:
for (int i = 0; i < names.length; i++) {
String newName = String.format("#%d %s", i + 1, names[i]);
System.out.println(newName);
}
Use Spliterators.spliterator() or Spliterators.spliteratorUnknownSize() to convert an iterator to a stream:
Iterator<String> iterator = Arrays.asList("A", "B", "C").iterator();
Spliterator<String> spliterator = Spliterators.spliteratorUnknownSize(iterator, 0);
Stream<String> stream = StreamSupport.stream(spliterator, false);
Variable declaration for examples:
Collection<String> abc = Arrays.asList("a", "b", "c");
Collection<String> digits = Arrays.asList("1", "2", "3");
Collection<String> greekAbc = Arrays.asList("alpha", "beta", "gamma");
Example 1 - Concatenate two Streams
final Stream<String> concat1 = Stream.concat(abc.stream(), digits.stream());
concat1.forEach(System.out::print);
// prints: abc123
Example 2 - Concatenate more than two Streams
final Stream<String> concat2 = Stream.concat(
Stream.concat(abc.stream(), digits.stream()),
greekAbc.stream());
System.out.println(concat2.collect(Collectors.joining(", ")));
// prints: a, b, c, 1, 2, 3, alpha, beta, gamma
Alternatively to simplify the nested concat() syntax the Streams can also be concatenated with flatMap():
final Stream<String> concat3 = Stream.of(
abc.stream(), digits.stream(), greekAbc.stream())
.flatMap(s -> s);
// or `.flatMap(Function.identity());` (java.util.function.Function)
System.out.println(concat3.collect(Collectors.joining(", ")));
// prints: a, b, c, 1, 2, 3, alpha, beta, gamma
Be careful when constructing Streams from repeated concatenation, because accessing an element of a deeply concatenated Stream can result in deep call chains or even a StackOverflowException.
Java does not have a Char Stream, so when working with Strings and constructing a Stream of Characters, an option is to get a IntStream (opens new window) of code points using String.codePoints() method. So IntStream can be obtained as below:
public IntStream stringToIntStream(String in) {
return in.codePoints();
}
It is a bit more involved to do the conversion other way around i.e. IntStreamToString. That can be done as follows:
public String intStreamToString(IntStream intStream) {
return intStream.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();
}
It is possible to find the first element of a Stream that matches a condition.
For this example, we will find the first Integer whose square is over 50000.
IntStream.iterate(1, i -> i + 1) // Generate an infinite stream 1,2,3,4...
.filter(i -> (i*i) > 50000) // Filter to find elements where the square is >50000
.findFirst(); // Find the first filtered element
This expression will return an OptionalInt with the result.
Note that with an infinite Stream, Java will keep checking each element until it finds a result. With a finite Stream, if Java runs out of elements but still can't find a result, it returns an empty OptionalInt.
Method references make excellent self-documenting code, and using method references with Streams makes complicated processes simple to read and understand. Consider the following code:
public interface Ordered {
default int getOrder(){
return 0;
}
}
public interface Valued<V extends Ordered> {
boolean hasPropertyTwo();
V getValue();
}
public interface Thing<V extends Ordered> {
boolean hasPropertyOne();
Valued<V> getValuedProperty();
}
public <V extends Ordered> List<V> myMethod(List<Thing<V>> things) {
List<V> results = new ArrayList<V>();
for (Thing<V> thing : things) {
if (thing.hasPropertyOne()) {
Valued<V> valued = thing.getValuedProperty();
if (valued != null && valued.hasPropertyTwo()){
V value = valued.getValue();
if (value != null){
results.add(value);
}
}
}
}
results.sort((a, b)->{
return Integer.compare(a.getOrder(), b.getOrder());
});
return results;
}
This last method rewritten using Streams and method references is much more legible and each step of the process is quickly and easily understood - it's not just shorter, it also shows at a glance which interfaces and classes are responsible for the code in each step:
public <V extends Ordered> List<V> myMethod(List<Thing<V>> things) {
return things.stream()
.filter(Thing::hasPropertyOne)
.map(Thing::getValuedProperty)
.filter(Objects::nonNull)
.filter(Valued::hasPropertyTwo)
.map(Valued::getValue)
.filter(Objects::nonNull)
.sorted(Comparator.comparing(Ordered::getOrder))
.collect(Collectors.toList());
}
When you have a Stream you need to map but want to preserve the initial values as well, you can map the Stream to a Map.Entry<K,V> using a utility method like the following:
public static <K, V> Function<K, Map.Entry<K, V>> entryMapper(Function<K, V> mapper){
return (k)->new AbstractMap.SimpleEntry<>(k, mapper.apply(k));
}
Then you can use your converter to process Streams having access to both the original and mapped values:
Set<K> mySet;
Function<K, V> transformer = SomeClass::transformerMethod;
Stream<Map.Entry<K, V>> entryStream = mySet.stream()
.map(entryMapper(transformer));
You can then continue to process that Stream as normal. This avoids the overhead of creating an intermediate collection.
Reduction is the process of applying a binary operator to every element of a stream to result in one value.
The sum() method of an IntStream is an example of a reduction; it applies addition to every term of the Stream, resulting in one final value:
(opens new window)
This is equivalent to (((1+2)+3)+4)
The reduce method of a Stream allows one to create a custom reduction. It is possible to use the reduce method to implement the sum() method:
IntStream istr;
//Initialize istr
OptionalInt istr.reduce((a,b)->a+b);
The Optional version is returned so that empty Streams can be handled appropriately.
Another example of reduction is combining a Stream<LinkedList<T>> into a single LinkedList<T>:
Stream<LinkedList<T>> listStream;
//Create a Stream<LinkedList<T>>
Optional<LinkedList<T>> bigList = listStream.reduce((LinkedList<T> list1, LinkedList<T> list2)->{
LinkedList<T> retList = new LinkedList<T>();
retList.addAll(list1);
retList.addAll(list2);
return retList;
});
You can also provide an identity element. For example, the identity element for addition is 0, as x+0==x. For multiplication, the identity element is 1, as x*1==x. In the case above, the identity element is an empty LinkedList<T>, because if you add an empty list to another list, the list that you are "adding" to doesn't change:
Stream<LinkedList<T>> listStream;
//Create a Stream<LinkedList<T>>
LinkedList<T> bigList = listStream.reduce(new LinkedList<T>(), (LinkedList<T> list1, LinkedList<T> list2)->{
LinkedList<T> retList = new LinkedList<T>();
retList.addAll(list1);
retList.addAll(list2);
return retList;
});
Note that when an identity element is provided, the return value is not wrapped in an Optional—if called on an empty stream, reduce() will return the identity element.
The binary operator must also be associative, meaning that (a+b)+c==a+(b+c). This is because the elements may be reduced in any order. For example, the above addition reduction could be performed like this:
This reduction is equivalent to writing ((1+2)+(3+4)). The property of associativity also allows Java to reduce the Stream in parallel—a portion of the Stream can be reduced by each processor, with a reduction combining the result of each processor at the end.
You may need to convert a Stream emitting Optional to a Stream of values, emitting only values from existing Optional. (ie: without null value and not dealing with Optional.empty() (opens new window)).
Optional<String> op1 = Optional.empty();
Optional<String> op2 = Optional.of("Hello World");
List<String> result = Stream.of(op1, op2)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
System.out.println(result); //[Hello World]
Example: Get a Stream of 30 elements, containing 21st to 50th (inclusive) element of a collection.
final long n = 20L; // the number of elements to skip
final long maxSize = 30L; // the number of elements the stream should be limited to
final Stream<T> slice = collection.stream().skip(n).limit(maxSize);
Notes:
Simple case without duplicate keys
Stream<String> characters = Stream.of("A", "B", "C");
Map<Integer, String> map = characters
.collect(Collectors.toMap(element -> element.hashCode(), element -> element));
// map = {65=A, 66=B, 67=C}
To make things more declarative, we can use static method in Function interface - Function.identity() (opens new window). We can replace this lambda element -> element with Function.identity().
Case where there might be duplicate keys
The javadoc (opens new window) for Collectors.toMap states:
If the mapped keys contains duplicates (according to Object.equals(Object)), an IllegalStateException is thrown when the collection operation is performed. If the mapped keys may have duplicates, use toMap(Function, Function, BinaryOperator) instead.
Stream<String> characters = Stream.of("A", "B", "B", "C");
Map<Integer, String> map = characters
.collect(Collectors.toMap(
element -> element.hashCode(),
element -> element,
(existingVal, newVal) -> (existingVal + newVal)));
// map = {65=A, 66=BB, 67=C}
The BinaryOperator passed to Collectors.toMap(...) generates the value to be stored in the case of a collision. It can:
Grouping by value
You can use Collectors.groupingBy (opens new window) when you need to perform the equivalent of a database cascaded "group by" operation. To illustrate, the following creates a map in which people's names are mapped to surnames:
List<Person> people = Arrays.asList(
new Person("Sam", "Rossi"),
new Person("Sam", "Verdi"),
new Person("John", "Bianchi"),
new Person("John", "Rossi"),
new Person("John", "Verdi")
);
Map<String, List<String>> map = people.stream()
.collect(
// function mapping input elements to keys
Collectors.groupingBy(Person::getName,
// function mapping input elements to values,
// how to store values
Collectors.mapping(Person::getSurname, Collectors.toList()))
);
// map = {John=[Bianchi, Rossi, Verdi], Sam=[Rossi, Verdi]}
List<String> data = new ArrayList<>();
data.add("Sydney");
data.add("London");
data.add("New York");
data.add("Amsterdam");
data.add("Mumbai");
data.add("California");
System.out.println(data);
List<String> sortedData = data.stream().sorted().collect(Collectors.toList());
System.out.println(sortedData);
Output:
[Sydney, London, New York, Amsterdam, Mumbai, California]
[Amsterdam, California, London, Mumbai, New York, Sydney]
It's also possible to use different comparison mechanism as there is a overloaded sorted (opens new window) version which takes a comparator as its argument.
Also, you can use a lambda expression for sorting:
List<String> sortedData2 = data.stream().sorted((s1,s2) -> s2.compareTo(s1)).collect(Collectors.toList());
This would output [Sydney, New York, Mumbai, London, California, Amsterdam]
You can use Comparator.reverseOrder() to have a comparator that imposes the reverse of the natural ordering.
List<String> reverseSortedData = data.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
Java provides specialized Streams for three types of primitives IntStream (for ints), LongStream (for longs) and DoubleStream (for doubles). Besides being optimized implementations for their respective primitives, they also provide several specific terminal methods, typically for mathematical operations. E.g.:
IntStream is = IntStream.of(10, 20, 30);
double average = is.average().getAsDouble(); // average is 20.0
Stream operations fall into two main categories, intermediate and terminal operations, and two sub-categories, stateless and stateful.
An intermediate operation is always lazy, such as a simple Stream.map. It is not invoked until the stream is actually consumed. This can be verified easily:
Arrays.asList(1, 2 ,3).stream().map(i -> {
throw new RuntimeException("not gonna happen");
return i;
});
Intermediate operations are the common building blocks of a stream, chained after the source and are usually followed by a terminal operation triggering the stream chain.
Terminal operations are what triggers the consumption of a stream. Some of the more common are Stream.forEach or Stream.collect. They are usually placed after a chain of intermediate operations and are almost always eager.
Statelessness means that each item is processed without the context of other items. Stateless operations allow for memory-efficient processing of streams. Operations like Stream.map and Stream.filter that do not require information on other items of the stream are considered to be stateless.
Statefulness means the operation on each item depends on (some) other items of the stream. This requires a state to be preserved. Statefulness operations may break with long, or infinite, streams. Operations like Stream.sorted require the entirety of the stream to be processed before any item is emitted which will break in a long enough stream of items. This can be demonstrated by a long stream (run at your own risk):
// works - stateless stream
long BIG_ENOUGH_NUMBER = 999999999;
IntStream.iterate(0, i -> i + 1).limit(BIG_ENOUGH_NUMBER).forEach(System.out::println);
This will cause an out-of-memory due to statefulness of Stream.sorted:
// Out of memory - stateful stream
IntStream.iterate(0, i -> i + 1).limit(BIG_ENOUGH_NUMBER).sorted().forEach(System.out::println);
A use case that comes across frequently, is creating a String from a stream, where the stream-items are separated by a certain character. The Collectors.joining() method can be used for this, like in the following example:
Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");
String result = fruitStream.filter(s -> s.contains("a"))
.map(String::toUpperCase)
.sorted()
.collect(Collectors.joining(", "));
System.out.println(result);
Output:
APPLE, BANANA, ORANGE, PEAR
The Collectors.joining() method can also cater for pre- and postfixes:
String result = fruitStream.filter(s -> s.contains("e"))
.map(String::toUpperCase)
.sorted()
.collect(Collectors.joining(", ", "Fruits: ", "."));
System.out.println(result);
Output:
Fruits: APPLE, ORANGE, PEAR.
Analog to get a collection for a Stream by collect() an array can be obtained by the Stream.toArray() method:
List<String> fruits = Arrays.asList("apple", "banana", "pear", "kiwi", "orange");
String[] filteredFruits = fruits.stream()
.filter(s -> s.contains("a"))
.toArray(String[]::new);
// prints: [apple, banana, pear, orange]
System.out.println(Arrays.toString(filteredFruits));
String[]::new is a special kind of method reference: a constructor reference.
It is sometimes useful to create random Strings, maybe as Session-ID for a web-service or an initial password after registration for an application.
This can be easily achieved using Streams.
First we need to initialize a random number generator. To enhance security for the generated Strings, it is a good idea to use SecureRandom.
Note: Creating a SecureRandom is quite expensive, so it is best practice to only do this once and call one of its setSeed() methods from time to time to reseed it.
private static final SecureRandom rng = new SecureRandom(SecureRandom.generateSeed(20));
//20 Bytes as a seed is rather arbitrary, it is the number used in the JavaDoc example
When creating random Strings, we usually want them to use only certain characters (e.g. only letters and digits). Therefore we can create a method returning a boolean which can later be used to filter the Stream.
//returns true for all chars in 0-9, a-z and A-Z
boolean useThisCharacter(char c){
//check for range to avoid using all unicode Letter (e.g. some chinese symbols)
return c >= '0' && c <= 'z' && Character.isLetterOrDigit(c);
}
Next we can utilize the RNG to generate a random String of specific length containing the charset which pass our useThisCharacter check.
public String generateRandomString(long length){
//Since there is no native CharStream, we use an IntStream instead
//and convert it to a Stream<Character> using mapToObj.
//We need to specify the boundaries for the int values to ensure they can safely be cast to char
Stream<Character> randomCharStream = rng.ints(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT).mapToObj(i -> (char)i).filter(c -> this::useThisCharacter).limit(length);
//now we can use this Stream to build a String utilizing the collect method.
String randomString = randomCharStream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString();
return randomString;
}
Read Article
https://develop.horje.com/learn/1434/reference