Java 8 introduces new features such as Streams.
Streams
Streams main purpose is to perform a set of operations on collections such as List, LinkedList, Maps, Sets. It also can be used with arrays or any kind of I/O.
What is a Stream Stream is a sequence of elements which can be created out of a collections
List<String> names = List.of("amy","dan")
names.stream(); //creates a stream
Usage of using stream api operations
Helper class for stream example
@Getter
@AllArgsConstructor
public class Student{
private String name;
private Integer gradeLevel;
private double gpa;
private String gender;
private Integer noteBooks;
private List<String> activities;
}
public class StudentRepository{
public static List<Student> getAllStudents() {
return List.of(
new Student("Adam", 2, 3.6, "male", 10, List.of("swimming", "basketball", "volleyball")),
new Student("Jenny", 2, 3.8, "female", 11, List.of("swimming", "gymnastics", "soccer")),
new Student("Emily", 3, 3.4, "female", 12, List.of("swimming", "gymnastics", "aerobics")),
new Student("Dave", 3, 3.4, "male", 12, List.of("swimming", "gymnastics", "aerobics")),
new Student("Sophia", 4, 4.3, "female", 10, List.of("swimming", "dancing", "football")),
new Student("James", 4, 4.6, "male", 22, List.of("swimming", "basketball", "baseball", "football"))
);
}
}
public class StreamsExample {
public static void main(String[] args) {
Predicate<Student> filterByGradeLevel = student -> student.getGradeLevel() >=3;
Predicate<Student> filterByGpa = student -> student.getGpa() >= 3.9;
Map<String, List<String>> studentMap =
StudentRepository.getAllStudents().stream()
.filter(filterByGradeLevel)
.filter(filterByGpa)
.collect(Collectors.toMap(Student::getName, Student::getActivities));
System.out.println(studentMap);
}
}
Output
{James=[swimming, basketball, baseball, football], Sophia=[swimming, dancing, football]}
Sample of stream flow
Debugging with stream
- A helpful tip for debugging streams is to use the
peek(Consumer)
operation. This enables us to stop at a certain stream operation to see what is the output
StudentRepository.getAllStudents().stream()
.filter(filterByGradeLevel)
.filter(filterByGpa)
.peek(System.out::println) //prints stream of students filtered by grade and gpa before being terminated
.collect(Collectors.toMap(Student::getName, Student::getActivities));
Streams API
map Returns a stream consisting of the results of applying the given function to the elements of this stream.
public static List<String> namesList(){
return StudentRepository.getAllStudents().stream()
.map(Student::getName)
.map(String::toUpperCase)
.collect(Collectors.toList());
}
public static void main(String[] args) {
System.out.println(namesList());
}
Output
[ADAM, JENNY, EMILY, DAVE, SOPHIA, JAMES]
flatMap
Converts one type to another similar to map() method except that it’s used in the context of Stream where each element in the stream represents multiple elements such Stream<List>
or Stream<Arrays
public class StreamFlatMapExample {
public static List<String> printStudentActivities(){
return StudentDataAccess.getAllStudents() //List<Student>
.stream() //Stream<Student>
.map(Student::getActivities) //Stream<List<String>>
.flatMap(List::stream) //Stream<String>
.collect(Collectors.toList()); //List<String>
}
public static void main(String[] args) {
System.out.println(printStudentActivities());
}
}
Output
[swimming, basketball, volleyball, swimming, gymnastics, soccer, swimming, gymnastics, aerobics, swimming, gymnastics, aerobics, swimming, dancing, football, swimming, basketball, baseball, football]
distinct Returns a stream with unique elements
public class StreamFlatMapExample {
public static List<String> printStudentActivities(){
return StudentDataAccess.getAllStudents() //List<Student>
.stream() //Stream<Student>
.map(Student::getActivities) //Stream<List<String>>
.flatMap(List::stream) //Stream<String>
.distinct()
.collect(Collectors.toList()); //List<String>
}
public static void main(String[] args) {
System.out.println(printStudentActivities());
}
}
Output
[swimming, basketball, volleyball, gymnastics, soccer, aerobics, dancing, football, baseball]
count Return a long with the total number of elements in the Stream
public static long getCountOfStudentActivities(){
return StudentDataAccess.getAllStudents()
.stream()
.map(Student::getActivities)
.flatMap(List::stream)
.distinct()
.count();
}
public static void main(String[] args) {
System.out.println("Count: " + getCountOfStudentActivities());
}
Output
Count: 9
sorted Sort the elements in the stream
There are 2 overloaded method for sorted
sorted()
and sorted(Comparator Function)
We could also chain sorted with reversed()
public class StreamFlatMapExample {
public static List<String> printStudentActivities(){
return StudentDataAccess.getAllStudents()
.stream()
.map(Student::getActivities)
.flatMap(List::stream)
.distinct()
.sorted()
.collect(Collectors.toList());
}
public static void main(String[] args) {
System.out.println(printStudentActivities());
}
Output
[aerobics, baseball, basketball, dancing, football, gymnastics, soccer, swimming, volleyball]
public class StreamFlatMapExample {
public static List<Student> sortStudentsByName(){
return StudentDataAccess.getAllStudents()
.stream()
.sorted(Comparator.comparing(Student::getName))
.collect(Collectors.toList());
}
public static void main(String[] args) {
sortStudentsByName().forEach(System.out::println);
}
Output
Student{name='Adam', gradeLevel=2, gpa=3.6, gender='male', noteBooks=10}
Student{name='Dave', gradeLevel=3, gpa=3.4, gender='male', noteBooks=12}
Student{name='Emily', gradeLevel=3, gpa=3.4, gender='female', noteBooks=12}
Student{name='James', gradeLevel=4, gpa=4.6, gender='male', noteBooks=22}
Student{name='Jenny', gradeLevel=2, gpa=3.8, gender='female', noteBooks=11}
Student{name='Sophia', gradeLevel=4, gpa=4.3, gender='female', noteBooks=10}
filter Filter the elements in the stream
public class StreamFilterExample {
public static List<Student> filterStudents(){
return StudentDataAccess.getAllStudents().stream()
.filter(student -> student.getGpa() >= 4.0)
.collect(Collectors.toList());
}
public static void main(String[] args) {
System.out.println(filterStudents());
}
}
Output
[Student{name='Sophia', gradeLevel=4, gpa=4.3, gender='female', noteBooks=10}, Student{name='James', gradeLevel=4, gpa=4.6, gender='male', noteBooks=22}]
reduce
This is a terminal operation. Used to reduce the contents of stream to a single value. reduce method without identity will return Optional
as a return type
public class StreamReduceExample {
public static int performMultiplication(List<Integer> integerList){
return integerList.stream()
//1,3,5,7
//a=1, b=1(stream) -> 1 is returned
//a=1, b=3(stream) -> 3 is returned
//a=3, b=5(stream) -> 15 is returned
//a=15, b=7(stream) -> 105 is returned
.reduce(1, (a,b) -> a*b);
}
public static Optional<Integer> performMultiplicationWithoutIdentity(List<Integer> integerList){
return integerList.stream()
//1,3,5,7
//a=1, b=1(stream) -> 1 is returned
//a=1, b=3(stream) -> 3 is returned
//a=3, b=5(stream) -> 15 is returned
//a=15, b=7(stream) -> 105 is returned
.reduce((a,b) -> a*b);
}
public static void main(String[] args) {
System.out.println(performMultiplication(List.of(1,3,5,7)));
Optional<Integer> result = performMultiplicationWithoutIdentity(List.of(1, 3, 5, 7));
result.ifPresent(System.out::println);
}
Output
105
105
limit & skip These two functions helps to create a sub-stream
- limit(n) - limits the n numbers of elements to be processed in the stream
- skip(n) - skips the n numbers of elements from the stream
public class StreamsLimitSkipExample {
public static Optional<Integer> limitList(List<Integer> integers){
return integers.stream()
.limit(2)
.reduce(Integer::sum);
}
public static Optional<Integer> skipList (List<Integer> integers){
return integers.stream()
.skip(2)
.reduce(Integer::sum);
}
public static void main(String[] args) {
List<Integer> integerList = List.of(6,7,8,9,10);
Optional<Integer> limitedList = limitList(integerList);
Consumer<Integer> displayStatement = (value) -> System.out.println("The limit result is : " + value);
limitedList.ifPresent(displayStatement);
Optional<Integer> skipList = skipList(integerList);
Consumer<Integer> skipStatement = (value) -> System.out.println("The skip result is : " + value);
skipList.ifPresent(skipStatement);
}
}
Output
The limit result is : 13
The skip result is : 27
allMatch & anyMatch & noneMatch
These functions takes in a predicatee
as in input and returns a boolean
as an output
anyMatch()
- Returns true if any one of the element matches the predicate, otherwise falseallMatch()
- Returns true if all the element in the stream matches the predicate, otherwise falsenoneMatch()
- Opposite toallMatch()
. Returnstrue
if none of the element in the stream amtches the predicate, otherwise false
public class StreamsMatchExample {
private static boolean allMatch() {
return StudentDataAccess.getAllStudents().stream()
.allMatch(student -> student.getGpa() >= 3.4);
}
private static boolean anyMatch() {
return StudentDataAccess.getAllStudents().stream()
.anyMatch(student -> student.getGpa() >= 4.0);
}
private static boolean noneMatch() {
return StudentDataAccess.getAllStudents().stream()
.noneMatch(student -> student.getGpa() >= 4.9);
}
public static void main(String[] args) {
System.out.println("Result of allMatch: " + allMatch());
System.out.println("Result of anyMatch: " + anyMatch());
System.out.println("Result of noneMatch: " + noneMatch());
}
}
Output
Result of allMatch: true
Result of anyMatch: true
Result of noneMatch: true
findFirst & findAny
Functions used to find an element in the stream and reutrns the result type of Optional
.
findFirst()
- Returns the first element in the streamfindAny()
- Returns the first encountered element in the stream
public class StreamsFindAnyFirstExample {
private static Optional<Student> findAny() {
return StudentDataAccess.getAllStudents().stream()
.filter(student -> student.getGpa() > 3.4)
.findAny();
}
private static Optional<Student> findFirst() {
return StudentDataAccess.getAllStudents().stream()
.filter(student -> student.getGpa() > 3.4)
.findFirst();
}
public static void main(String[] args) {
Optional<Student> anyStudent = findAny();
anyStudent.ifPresent((student) -> System.out.println("Any student: " + student));
Optional<Student> firstStudent = findFirst();
firstStudent.ifPresent((student) -> System.out.println("First student: " + student));
}
}
Output
Any student: Student{name='Adam', gradeLevel=2, gpa=3.6, gender='male', noteBooks=10}
First student: Student{name='Adam', gradeLevel=2, gpa=3.6, gender='male', noteBooks=10}
Terminal Operations
Terminal operations collects the data and starts the whole stream pipeline
- forEach
- reduce
- collect
- etc..
joining()
- Collector performs the String concatenation on the elements in the stream
- We can use it with delimiter, prefix and suffix
public class StreamsJoiningExample {
public static String joiningDefault(){
return StudentDataAccess.getAllStudents().stream()
.map(Student::getName)
.collect(joining());
}
public static String joiningWithDelimiter(){
return StudentDataAccess.getAllStudents().stream()
.map(Student::getName)
.collect(joining("|"));
}
public static String joiningWithPrefixAndSuffix(){
return StudentDataAccess.getAllStudents().stream()
.map(Student::getName)
.collect(joining("-", "(", ")"));
}
public static void main(String[] args) {
System.out.println(joiningDefault());
System.out.println(joiningWithDelimiter());
System.out.println(joiningWithPrefixAndSuffix());
}
}
groupingBy()
- use to group elements based on a property
- output of groupingBy is going to be
Map<K,V>
- 3 overloaded methods
groupingBy(classifier)
groupingBy(classifier,downstream reduction function)
groupingBy(classifier,supplier,downstream reduction function)
public class StreamsGroupingByExample {
public static void categoriseByGender() {
Map<String, List<Student>> collect = StudentDataAccess.getAllStudents()
.stream()
.collect(groupingBy(Student::getGender));
System.out.println("Categorise by gender: " + collect);
}
public static void categoriseByGenderWithGpa() {
Map<String, List<Student>> collect = StudentDataAccess.getAllStudents()
.stream()
.collect(groupingBy(student -> student.getGpa() >= 3.8 ? "OUTSTANDING" : "AVERAGE"));
System.out.println("Categorise by genderWithGPA: " + collect);
}
public static void twoLevelGrouping() {
Map<Integer, Map<String, List<Student>>> collect = StudentDataAccess.getAllStudents()
.stream()
.collect(groupingBy(Student::getGradeLevel,
groupingBy(student -> student.getGpa() >= 3.8 ? "OUTSTANDING" : "AVERAGE")));
System.out.println("Two level grouping: " + collect);
}
public static void mapStudentToNotebooks() {
Map<String, Integer> collect = StudentDataAccess.getAllStudents()
.stream()
.collect(groupingBy(Student::getName,
summingInt(Student::getNoteBooks)));
System.out.println("mapStudentToNotebooks: "+ collect);
}
//With classifier, supplier, and collector downstream function
public static void threeArgumentGroupBy() {
LinkedHashMap<String, Set<Student>> collect = StudentDataAccess.getAllStudents()
.stream()
.collect(groupingBy(Student::getName, LinkedHashMap::new,
toSet()));
System.out.println("threeArgumentGroupBy: " + collect);
}
public static void calculateTopGpa(){
Map<Integer, Student> collect = StudentDataAccess.getAllStudents()
.stream()
.collect(groupingBy(Student::getGradeLevel,
collectingAndThen(maxBy(Comparator.comparing(Student::getGpa)),
Optional::get)));
System.out.println("Top GPA: " + collect);
}
public static void calculateLeastGpa(){
Map<Integer, Student> collect = StudentDataAccess.getAllStudents()
.stream()
.collect(groupingBy(Student::getGradeLevel,
collectingAndThen(minBy(Comparator.comparing(Student::getGpa)),
Optional::get)));
System.out.println("Least GPA: " + collect);
}
public static void main(String[] args) {
categoriseByGender();
categoriseByGenderWithGpa();
twoLevelGrouping();
mapStudentToNotebooks();
threeArgumentGroupBy();
calculateTopGpa();
calculateLeastGpa();
}
}
partitioningBy
- accepts a predicate as an input
- Retrun a
Map<K,V
. Key of return type is going to be aboolean
- 2 overloaded methods
partitioningBy(predicate)
partitioningBy(predicate, downstream)
downstream can be any kind of collector
public class StreamsPartitioningByExample {
public static void partitioningBy1(){
Predicate<Student> gpaPredicate = student -> student.getGpa() >= 3.8;
Map<Boolean, List<Student>> collect = StudentDataAccess.getAllStudents().stream()
.collect(Collectors.partitioningBy(gpaPredicate));
System.out.println(collect);
}
public static void partitioningBy2(){
Predicate<Student> gpaPredicate = student -> student.getGpa() >= 3.8;
Map<Boolean, Set<Student>> collect = StudentDataAccess.getAllStudents().stream()
.collect(Collectors.partitioningBy(gpaPredicate,
Collectors.toSet()));
System.out.println(collect);
}
public static void main(String[] args) {
partitioningBy1();
partitioningBy2();
}
}