Java Streams
All of us have watched online videos on Youtube. When we start watching a video, a small portion of the video file is first loaded into our computer and starts playing. we don’t need to download the complete video before we start watching it. This is called video streaming.
At a very high level, we can think of that small portion of the video file as a Stream and the whole video as a Collection
In Java, java.util.Stream
interface represents a stream on which one or more
operations can be performed. Stream operations are either intermediate or
terminal.
-
Intermediate operations return a stream. so we can chain multiple intermediate operations without using semicolons.
-
Terminal operations are either void or return a non-stream result
Streams are created on a source, e.g. a java.util.Collection
like List or
Set. The Map is not supported directly, we can create stream of map
keys, values or entries. Stream operations can either be executed
sequentially or parallel. when performed parallelly, it is called a
parallel stream.
How Stream Work
List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");
myList.stream()
.filter(s -> s.startsWith("c"))
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);
In above filter, map and sorted are intermediate operations whereas forEach is a terminal operation.
filter, map and sorted all will return Stream object only.
Streams can be created from various data sources, especially collections. Lists and Sets support new methods stream() and parallelStream() to either create a sequential or a parallel stream.Parallel streams can operate on multiple threads.
The features of Java stream are
-
A stream is not a data structure.
-
it takes input from the Collections, Arrays or I/O channels.
-
Streams don’t change original data structure; they only provide the result as per the pipelined methods.
-
Do not support indexed access
-
Lazy access supported
-
Parallelizable
-
Each intermediate operation is lazily executed and returns a stream as a result. hence various intermediate operations can be pipelined. Terminal operations mark the end of the stream and return the result.
1. Creating Streams
XxxStream.of() : Create Steam using Primitive types (int, char, byte, double)
IntStream is = IntStream.of(3, 4, 5, 6);
DoubleStream is = DoubleStream.of(3.1, 4.0, 5.7, 6.0);
Stream.of() : Create Steam using Primitive Object types (Int, String, Wrapper types )
//Primitive Types
Stream<Integer> intStream= Stream.of(1,2,3,4,5,6,7,8);
Stream<Character> charStream = Stream.of('A','B','C','D','E');
Stream<String> strStream = Stream.of("Aaa", "Bbbb", "Cccc", "Dddd");
IntStream is a stream of primitive int values.
Stream<Integer> is a stream of Integer objects
Stream.of(array) : Create Steam using Array types (Int, String)
Stream<Integer> intArraystream = Stream.of( new Integer[]{1,2,3,4,5,6,7,8,9} );
Stream<Character> charArraystream = Stream.of( new Character[]{'A','B','C','D','E'} );
Stream<String> strArraystream = Stream.of( new String[]{"Aaa", "Bbbb", "Cccc", "Dddd"} );
List.stream() : Create Steam from Collection types (List, Set, Not MAP)
//Collection types
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
Stream<String> strListStream = list.stream();
List<Integer> list2 = new ArrayList<Integer>();
list2.add(1);
list2.add(2);
list2.add(3);
Stream<Integer> intListStream = list2.stream();
Ex: Create Steam by Splitting String
Stream<String> splitStream = Stream.of(“A$B$C$D”.split(“\\$”));
2. Stream Operations
Stream Operations: Intermediate
filter(Predicate<T>) | The elements of the stream matching the predicate (Condition) |
---|---|
map(Function<T, U>) | Performs some operation on each element & return something (add, multiply, convert) |
flatMap(Function<T, Stream<U>> | The elements of the streams resulting from applying the provided stream-bearing function to the elements of the stream |
distinct() | The elements of the stream, with duplicates removed |
sorted() | The elements of the stream, sorted in natural order |
Sorted(Comparator<T>) | The elements of the stream, sorted by the provided comparator |
limit(long) | The elements of the stream, truncated to the provided length |
skip(long) | The elements of the stream, discarding the first N elements |
takeWhile(Predicate<T>) | (The elements of the stream, truncated at the first element for which the provided predicate is not true |
dropWhile(Predicate<T>) | (Java 9 only) The elements of the stream, discarding the initial segment of elements for which the provided predicate is true |
Terminal Operations
forEach(Consumer<T> action) | Apply the provided action to each element of the stream. |
---|---|
toArray() | Create an array from the elements of the stream. |
reduce(…) | Aggregate the elements of the stream into a summary value. |
collect(…) | Aggregate the elements of the stream into a summary result container. |
min(Comparator<T>) | Return the minimal element of the stream according to the comparator. |
max(Comparator<T>) | Return the maximal element of the stream according to the comparator. |
count() | Return the size of the stream. |
{any,all,none}Match(Predicate<T>) | Return any/all/none of the elements of the stream match the predicate. |
findFirst() | Return the first element of the stream, if present. |
findAny() | Return any element of the stream, if present. |
Collectors & collect method
java.util.stream
|
Interface Collector
|
class Collectors
Collectors class : Implementations of Collector that implement various useful reduction operations, such as gathering elements into collections, summarizing elements according to various criteria, etc.
Some of Useful methods:
To Collections | Math Operations | Map Grouping |
---|---|---|
toCollection(Supplier) toList() toSet() toMap(Function, Function) joining() mapping(Function, Collector) filtering(Predct, Collector) | counting() minBy(Comparator) maxBy(Comparator) summingInt(ToIntFunction), summingLong(ToLongFunction), averagingInt(ToIntFunction), averagingLong(ToLongFunction) | groupingBy(Function) groupingByConcurrent(Function) partitioningBy(Predicate) reducing(BinaryOperator) |
Finally, most commonly used Operations
Following are Country names as List – common for all below Operations
List<String> list = new ArrayList<String>();
list.add("America");
list.add("China");
list.add("Japan");
list.add("Germany");
list.add("India");
list.add("Italy");
list.add("Russia");
list.add("Sweden");
list.add("Ukraine");
list.add("India");
list.add("Italy");
Intermediate Operations:
1. Stream.forEach(consumer):
Printing list using foreach
list.stream().forEach(System.out::println);
2. Stream.filter()
The filter() method accepts a Predicate to filter all elements of the stream. This operation is intermediate which enables us to call another stream operation (e.g. forEach()) on the result.
System.out.println("\n \nPrint Countries name Start with I");
list.stream()
.filter((n)->n.startsWith("I"))
.forEach(System.out::println);
Print Countries name Start with I
India
Italy
India
Italy
3. Stream.map()
The map() intermediate operation converts each element in the stream into another object via the given function. The following example converts each string into an UPPERCASE string. But we can use map() to transform an object into another type as well.
System.out.println("\n \n Convert to Upper case");
list.stream()
.filter((n) -> n.startsWith("I"))
.map((n) -> n.toUpperCase())
.map((String::toUpperCase)) //Using reference <RETURN TYPE> :: Opration
.forEach(System.out::println);
4. Stream.sorted()
The sorted() method is an intermediate operation that returns a sorted view of the stream. The elements in the stream are sorted in natural order unless we pass a custom Comparator.
System.out.println("\n \n Convert to Upper case & Sort");
list.stream()
.map(String::toUpperCase)
.sorted()
.forEach(System.out::println);
list.stream()
.map(String::toUpperCase)
.sorted(Comparator.reverseOrder())
.forEach(System.out::println);
To Sort User Object based on Age
List<User> sortedList = users.stream()
.sorted(Comparator.comparingInt(User::getAge)
.reversed())
.collect(Collectors.toList());
sortedList.forEach(System.out::println);
To Sort User Object based on Name
List<User> sortedList = users.stream()
.sorted(Comparator.comparing(User::getName))
.collect(Collectors.toList());
5. Stream.distinct()
To Remove duplicate items from Stream. Javadocs say that distinct() - Returns a stream consisting of the distinct elements (according to Object.equals(Object)) of this stream. In case of object types we need to generate hashcode to made objects equal.
System.out.println("\n \n List of Strings Start with I");
List tempList = list.stream()
.filter((n)-> n.startsWith("I"))
.distinct()
.collect(Collectors.toList());
System.out.println(tempList);
Terminal Operations:
forEach: The forEach method is used to iterate through every element of the
stream.
List number = Arrays.asList(2,3,4,5);
number.stream().map(x->x*x).forEach(y->System.out.println(y));
5. Stream.collect() :: Collection
The collect() method is used to receive elements from a steam and store them in a collection.
To Collections | Math Operations | Map Grouping |
---|---|---|
toCollection(Supplier) toList() toSet() toMap(Function, Function) joining() mapping(Function, Collector) filtering(Predct, Collector) | counting() minBy(Comparator) maxBy(Comparator) summingInt(ToIntFunction), summingLong(ToLongFunction), averagingInt(ToIntFunction), averagingLong(ToLongFunction) | groupingBy(Function) groupingByConcurrent(Function) partitioningBy(Predicate) reducing(BinaryOperator) |
System.out.println("\n \n List of Strings Start with I");
List tempList = list.stream()
.filter((n)-> n.startsWith("I"))
.collect(Collectors.toList());
System.out.println(tempList);
6. Stream.match() :: boolean
Various matching operations can be used to check whether a given predicate matches the stream elements. All of these matching operations are terminal and return a boolean result.
boolean matchedResult = memberNames.stream()
.anyMatch((s) -> s.startsWith("A"));
System.out.println(matchedResult); //true
matchedResult = memberNames.stream()
.allMatch((s) -> s.startsWith("A"));
System.out.println(matchedResult); //false
matchedResult = memberNames.stream()
.noneMatch((s) -> s.startsWith("A"));
System.out.println(matchedResult); //false
7. Stream.count() :: Integer/Any Number
The count() is a terminal operation returning the number of elements in the stream as a long value.
long totalMatched = memberNames.stream()
.filter((s) -> s.startsWith("A"))
.count();
System.out.println(totalMatched); //2
8. Stream.reduce()
The reduce() method performs a reduction on the elements of the stream with the given function. The result is an Optional holding the reduced value.
In the given example, we are reducing all the strings by concatenating them using a separator #.
Optional<String> reduced = memberNames.stream()
.reduce((s1,s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
9. Stream.anyMatch() :: Boolean
The anyMatch() will return true once a condition passed as predicate satisfies. Once a matching value is found, no more elements will be processed in the stream.
In the given example, as soon as a String is found starting with the letter ‘A’, the stream will end and the result will be returned.
boolean matched = memberNames.stream()
.anyMatch((s) -> s.startsWith("A"));
System.out.println(matched); //true
10. Stream.findFirst()
The findFirst() method will return the first element from the stream and then it will not process any more elements.
String firstMatchedName = memberNames.stream()
.filter((s) -> s.startsWith("L"))
.findFirst().get();
System.out.println(firstMatchedName); //Lokesh
11. flatMap()
flattening is referred to as merging multiple collections/arrays into one
flatMap() = Flattening + map()
Stream.flatMap() helps in converting Stream<Collection<T>> to Stream<T>.
Flattening example 1
Before flattening : [[1, 2, 3], [4, 5], [6, 7, 8]]
After flattening : [1, 2, 3, 4, 5, 6, 7, 8]
List <Integer> list1 = Arrays.asList(1, 2, 3);
List <Integer> list2 = Arrays.asList(4, 5, 6);
List <Integer> list3 = Arrays.asList(7, 8, 9);
List <List <Integer >> listOfLists = Arrays.asList(list1, list2, list3);
List < Integer > listOfAllIntegers = listOfLists.stream()
.flatMap(x -> x.stream())
.collect(Collectors.toList());
System.out.println(listOfAllIntegers);
12. toArray
Employee[] employeesArray = employeeList.stream()
.filter(e -> e.getSalary() < 400)
.toArray(Employee[]::new);
Example
class Product{
int id;
String name;
float price;
public Product(int id, String name, float price) {
this.id = id;
this.name = name;
this.price = price;
}
}
public class Streams {
public static void main(String[] args) {
List<Product> productsList = new ArrayList<Product>();
//Adding Products
productsList.add(new Product(1,"HP Laptop",25000f));
productsList.add(new Product(2,"Dell Laptop",30000f));
productsList.add(new Product(3,"Lenevo Laptop",28000f));
productsList.add(new Product(4,"Sony Laptop",28000f));
productsList.add(new Product(5,"Apple Laptop",90000f));
List<Float> productPriceList2 =productsList.stream()
.filter(p -> p.price > 30000)// filtering data
.map(p->p.price) // fetching price
.collect(Collectors.toList()); // collecting as list
System.out.println(productPriceList2); //[90000.0]
// This is more compact approach for filtering data
productsList.stream()
.filter(product -> product.price == 30000)
.forEach(product -> System.out.println(product.name)); // Dell Laptop
// count number of products based on the filter
long count = productsList.stream()
.filter(product->product.price<30000)
.count();
System.out.println(count); //3
// Converting product List into Set
Set<Float> productPriceList = productsList.stream()
.filter(product->product.price < 30000) // filter product on the base of price
.map(product->product.price) //get the price
.collect(Collectors.toSet()); // collect it as Set(remove duplicate elements)
System.out.println(productPriceList); //[25000.0, 28000.0]
}
}
3. Parallel Streams
-
Sequential or Normal streams just work like for-loop using a single core. Parallel streams divide the provided task(like fork& join) into many and run them in different threads, utilizing multiple cores of the computer.
-
In parallel execution, if number of tasks are more than available cores at a given time, the remaining tasks are queued waiting for currently running task to finish.
-
In above-listed stream examples, anytime we want to do a particular job using multiple threads in parallel cores, all we have to call parallelStream() method instead of stream() method.
//For ArrayTypes
Stream<Integer> parllelStream =Stream.of(nums).parallel();
//For List Types
Stream<String> stream = list.parallelStream();
Integer[] nums = {1,1, 2, 3,3, 4, 5, 6, 7, 8, 9, 10};
System.out.println("\n \n Print Strings using normal Stream");
Stream<Integer> seqStream =Stream.of(nums);
seqStream.distinct()
.sorted(Comparator.reverseOrder())
.forEach((n)-> {
System.out.println(Thread.currentThread().getName()+" : "+n);
});
Print Strings using normal Stream
main : 10
main : 9
main : 8
main : 7
main : 6
main : 5
main : 4
main : 3
main : 2
main : 1
System.out.println("\n \n Print Strings using Parllel Stream");
Stream<Integer> parllelStream =Stream.of(nums).parallel();
parllelStream.distinct()
.sorted(Comparator.reverseOrder())
.forEach((n)-> {
System.out.println(Thread.currentThread().getName()+" : "+n);
});
Print Strings using Parllel Stream
main : 4
ForkJoinPool.commonPool-worker-7 : 6
ForkJoinPool.commonPool-worker-5 : 9
ForkJoinPool.commonPool-worker-9 : 2
ForkJoinPool.commonPool-worker-3 : 3
ForkJoinPool.commonPool-worker-15 : 1
ForkJoinPool.commonPool-worker-11 : 8
ForkJoinPool.commonPool-worker-13 : 5
ForkJoinPool.commonPool-worker-5 : 10
ForkJoinPool.commonPool-worker-7 : 7
In Above we are using parllelStream, So order is not correct, because the steam is processed by many threads.
Examples
Creating Streams | Interamediate | Terminal Operations |
---|---|---|
concat() | filter() | forEach() |
empty() | map() | forEachOrdered() |
generate() | flatMap() | toArray() |
iterate() | distinct() | reduce() |
of() | sorted() | collect() |
peek() | min() | |
limit() | max() | |
skip() | count() | |
anyMatch() | ||
allMatch() | ||
noneMatch() | ||
findFirst() | ||
findAny() |
1.Count Occurrences of a Char in a String
String someString = "elephant";
long count = someString
.chars()
.filter(ch -> ch == 'e')
.count();
Using map:
-
Invoke the chars() method on the input string and which returns the IntStream instance. This int stream holds the integer representation of each character in the string.
-
Need to convert IntStream to CharStream using the mapToObj() method.
-
Last, need to group by characters by calling Collectors.groupingBy() and to count call Collectors.counting() method.
String input = "success";
IntStream intStream = input.chars();
Stream<Character> stream = intStream.mapToObj(ch -> (char) ch);
Map<Character, Long> output = stream.collect(Collectors.groupingBy(ch -> ch, Collectors.counting()));
System.out.println(output);
//Single Line
Map<Character, Long> output2 = input.chars() .mapToObj(ch -> (char) ch) .collect(Collectors.groupingBy(ch -> ch, Collectors.counting() ) );
System.out.println(output2);
Counting Empty String
List<String> strList = Arrays.asList("abc", "", "bcd", "", "defg", "jk");
long c1 = strList.stream()
.filter(s -> s.isEmpty())
.count();
System.out.println(c1);//2
Map & joining
System.out.println("Count String whose length is more than three");
c1 = strList.stream().filter(s -> s.length()>3).count();
System.out.println(c1);//1
System.out.println("Remove all empty Strings from List");
List l1 = strList.stream()
.filter(s -> !s.isEmpty())
.collect(Collectors.toList());
System.out.println("Convert Strings in a list to uppercase and Join them with coma");
String join = strList.stream()
.map(s -> s.toUpperCase())
.collect(Collectors.joining(","));
Statistics: Get count, min, max, sum, and the average for numbers
we will learn how to get some statistical data from Collection, e.g. finding the minimum or maximum number from List, calculating the sum of all numbers from a numeric list, or calculating the average of all numbers from List.
Since these statistics operations are numeric in nature, it’s essential to call the mapToInt() method. After this, we call the summaryStatistics(), which returns an instance of an IntSummaryStatistics.
It is this object which provides us utility method like getMin(), getMax(), getSum() or getAverage().
List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
c1 = primes.stream()
.mapToInt(s -> s)
.summaryStatistics()
.getSum();
System.out.println(c1);
List<Integer> primes1 = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
IntSummaryStatistics stats = primes1.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Highest prime number in List : " + stats.getMax());
System.out.println("Lowest prime number in List : " + stats.getMin());
System.out.println("Sum of all prime numbers : " + stats.getSum());
System.out.println("Average of all prime numbers : " + stats.getAverage());
Ref.
https://howtodoinjava.com/java/stream/java-streams-by-examples/
https://www.java67.com/2014/04/java-8-stream-examples-and-tutorial.html
https://javabypatel.blogspot.com/2018/06/java-8-stream-practice-problems.html