Streams
Source
Array, Collection, Generator function, I/O channel
Object Streams
// native support
List< String > list = Arrays. asList ( "a" , "b" );
Stream< String > stream = list. stream ();
Person [] peopleArray = {
new Person ( "John" , "Boston" , 21 ),
new Person ( "Mary" , "Boston" , 29 )
};
// Both are same
Stream< Person > stream1 = Stream. of (peopleArray);
Stream< Person > stream2 = Arrays. stream (peopleArray);
// Warning!!!!
// Never pass a primitive array (int[]) to Stream.of().
// It creates a Stream<int[]> with a size of 1
Stream< int []> badStream = Stream. of (arr);
Stream< String > words = Stream. of ( "hello" , "world" );
Primitive Streams
Only 3 Specialized Primitive Streams
IntStream
used for byte, short, char, int
LongStream
DoubleStream
From Primitive Arrays
int [] arr = { 4 , 3 , 7 };
IntStream stream = Arrays. stream (arr);
IntStream intStream = IntStream. of ( 1 , 2 , 3 , 4 , 5 );
// range: [0, 10) = [0, 9]
IntStream zeroToNine = IntStream. range ( 0 , 10 );
// range: [0, 10]
IntStream zeroToTen = IntStream. rangeClosed ( 0 , 10 );
Boxing and Unboxing
stream.boxed()
IntStream —> Stream<Integer>
stream.mapToInt()
Stream<Integer> —> IntStream
int [] nums = { 4 , 3 , 7 , 8 , 1 };
// Boxing: IntStream -> Stream<Integer> -> List<Integer>
List< Integer > numbers = Arrays. stream (nums). boxed (). toList ();
// Unboxing: List<Integer> -> Stream<Integer> -> IntStream -> int[]
int [] finalNums = numbers. stream (). mapToInt (n -> n). toArray ();
https://stackoverflow.com/questions/47688418/what-is-the-difference-between-intermediate-and-terminal-operations
Intermediate:
filter(Predicate)
map(Function)
mapToInt(ToIntFunction)
mapToLong(ToLongFunction)
mapToDouble(ToDoubleFunction)
flatMap(Function)
flatMapToInt(ToIntFunction)
flatMapToLong(ToLongFunction)
flatMapToDouble(ToDoubleFunction)
sorted() same as sorted(Comparator.naturalOrder())
peek(Consumer)
distinct()
limit(long n)
skip(long n)
Terminal:
collect(Collector)
toList() — Returns unmodifiable list
Not same as collect(Collectors.toUnmodifiableList())
For modifiable list: collect(Collectors.toList())
toArray()
forEach(Consumer)
forEachOrdered(Consumer)
reduce(BinaryOperator)
min(Comparator)
max(Comparator)
count()
Short Circuiting:
Can stop processing elements early if match found
anyMatch(Predicate)
allMatch(Predicate)
noneMatch(Predicate)
findFirst() — Returns Optional
findAny() — Returns Optional
Not supported by Primitive streams
collect(Collector) —> Must call .boxed() first
toList() —> Must call .boxed().toList()
sorted(Comparator) —> Only supports no-argument .sorted()
min(Comparator) / max(Comparator) —> Overloaded to accept NO arguments
Primitive-Only Terminal Operators
sum()
average()
summaryStatistics()
getAverage()
getCount()
getSum()
getMin()
getMax()
asDoubleStream()
asLongStream()
Collectors
These are static factory methods of the Collectors interface
It is consumed by collect(Collector) stream terminal method and as a downstreamCollector in another Collector argument
Grouping and Partitioning
Grouping:
Returns: Map<Key, List<>>
groupingBy(keyMapper)
groupingBy(keyMapper, downstreamCollector)
groupingBy(keyMapper, mapFactory, downstreamCollector)
mapFactory specifies custom Map type like TreeMap::new
Partitioning
Returns: Map<Boolean, List<>>
Splits exclusively by true/false
partitioningBy(Predicate)
partitioningBy(Predicate, downstreamCollector)
Data Structure Collections
toList()
toSet()
toMap(keyMapper, valueMapper)
toMap(keyMapper, valueMapper, mergeFunction)
Crucial for handling duplicate key conflicts
toMap(keyMapper, valueMapper, mergeFunction, mapFactory)
mapFactory specifies custom Map type like TreeMap::new
toUnmodifiableList() (Java 10+)
Aggregations
joining(delimiter) — String case
reducing(T, BinaryOperator)
collectingAndThen(downstreamCollector, finisherFunction)
counting()
minBy(Comparator)
maxBy(Comparator)
Mathematical Summaries
Sum
summingInt(mapper)
summingLong(mapper)
summingDouble(mapper)
Average
averagingInt(mapper)
averagingLong(mapper)
averagingDouble(mapper)
Summary Statistics
summarizingInt(mapper)
summarizingLong(mapper)
summarizingDouble(mapper)
Returns a SummaryStatistics object containing methods
getAverage()
getCount()
getSum()
getMin()
getMax()
Examples
Sum and avg. of all the integers
int [] nums = { 4 , 3 , 7 , 8 , 1 };
System.out. println (Arrays. stream (nums). sum ()); // IntStream.count()
List< Integer > numbers = Arrays. stream (nums) // IntStream to List
. boxed ()
. toList ();
// Using mapToInt()
numbers. stream ()
. mapToInt (n -> n). sum ();
// Using Collectors.summingInt()
numbers. stream ()
. collect (Collectors. summingInt (n -> n));
// Avg
numbers. stream ()
. mapToDouble (Integer :: doubleValue) // !important
. average ()
. orElse ( 0.0 );
String based
// Uppercase all strings
var names = Arrays. asList ( "Sachin" , "manisH" , "Suresh" );
final List< String > uppercaseNames = names. stream ()
. map (String :: toUpperCase)
. toList ();
// count of strings with length > 5
List< String > list = new ArrayList<>(List. of ( "he" , "hello" , "kiamotors" , "helloworld" , "hd" ));
long count = list. stream (). filter (e -> e. length () > 5 ). count ();
Data Setup
@ Getter
@ Setter
@ AllArgsConstructor
class Employee {
private int id;
private String name;
private int age;
private String departNames;
private String address;
private double salary;
private String gender;
}
List< Employee > employees = Arrays. asList (
new Employee ( 1 , "Abraham" , 29 , "IT" , "Mumbai" , 20000 , "Male" ),
new Employee ( 2 , "Mary" , 27 , "Sales" , "Chennai" , 25000 , "Female" ),
new Employee ( 3 , "Joe" , 28 , "IT" , "Chennai" , 22000 , "Male" ),
new Employee ( 4 , "John" , 29 , "Sales" , "Gurgaon" , 29000 , "Male" ),
new Employee ( 5 , "Liza" , 25 , "Sales" , "Bangalore" , 32000 , "Female" ),
new Employee ( 6 , "Peter" , 27 , "Admin" , "Mumbai" , 31500 , "Male" ),
new Employee ( 7 , "Harry" , 30 , "Research" , "Kochi" , 21000 , "Male" )
);
Max age of employees
employees. stream ()
. mapToInt (Employee :: getAge)
. max (). orElse ( - 1 );
Count of employee in each department
employees. stream (). collect (
Collectors. groupingBy (Employee :: getDepartment, Collectors. counting ())
);
Second Lowest/Highest Salary
var secondLowestSalaryEmployee = employees. stream ()
. sorted (Comparator. comparing (Employee :: getSalary))
. skip ( 1 )
. findFirst (). orElse ( null );
var secondHighestSalaryEmployee = employees. stream ()
. sorted (Comparator. comparing (Employee :: getSalary). reversed ())
. skip ( 1 )
. findFirst (). orElse ( null );
Department with maximum number of employees
employees. stream ()
. collect (Collectors. groupingBy (
Employee :: getDepartment,
Collectors. counting ()))
. entrySet (). stream ()
. max (Comparator. comparingLong (Map.Entry :: getValue))
. map (Map.Entry :: getKey). orElse ( null );
Average age of each gender
employees. stream ()
. collect (Collectors. groupingBy (
Employee :: getGender,
Collectors. averagingInt (Employee :: getAge)
));
Find first non-repeating character using streams
String str = "karnataka" ;
List< String > characters = Arrays. asList (str. split ( "" ));
Map< String , Long > freq = characters. stream (). collect (Collectors. groupingBy (Function. identity (), Collectors. counting ()));
String result = characters. stream ()
. filter (c -> freq. get (c). equals ( 1L )) // compare with Long else will fail
. findFirst (). orElse ( null );
System.out. println (freq);
System.out. println (result);
Optimized using LinkedHashMap
It remembers the insertion order
String result = characters. stream ()
. collect (Collectors. groupingBy (Function. identity (), LinkedHashMap ::new , Collectors. counting ()))
. entrySet (). stream ()
. filter (entry -> entry. getValue () == 1L )
. map (entry -> entry. getKey ())
. findFirst (). orElse ( null );
System.out. println (result);