Advance Streams Operations

Plethora of different operations available with streams. Some basic and very important operations are already been discussed in previous article (read it from here). Let’s see how collect, flatMap and reduce operations works.

How to use Collect operation in streams?

Collect is used to transform the elements of the stream into a different data structures such as List, Set and Map. Collect accepts a collector which is based on operations like supplier, accumulator, combiner and finisher. The following example shows how to construct a list from stream elements and prints the result on the console.

package java8;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class Student {
	String name;
    int age;

    Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return name;
    }
    
    public static void main(String[] args){
    	//Create an list of student of objects
    	ArrayList stdList = new ArrayList();
    	stdList.add(new Student("Peter",22));
    	stdList.add(new Student("Sara",23));
    	stdList.add(new Student("Danial",21));
    	stdList.add(new Student("Siemen",22));
    	
    	//use stream operation to filter student name started with S
    	List filtered =
    			stdList
    		        .stream()
    		        .filter(s -> s.name.startsWith("S"))
    		        .collect(Collectors.toList());

    	//Print the list filtered using stream operation
    		System.out.println(filtered);    
    }
}

Output :

[Sara, Siemen]

Another example used to group student  and construct a list from streams elements, averaging the student age and displaying. In another example, a more comprehensive statistics is been returned by a summarizing collector containing min, max, total number of students, sum on age and average age  of student. Further example is using join the student who met a specific condition and using delimiter to separate each student.

package java8;

import java.util.ArrayList;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Student {
	String name;
	int age;

	Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return name;
	}

	public static void main(String[] args) {
		// Create an list of student of objects
		ArrayList stdList = new ArrayList();
		stdList.add(new Student("Peter", 22));
		stdList.add(new Student("Sara", 23));
		stdList.add(new Student("Danial", 21));
		stdList.add(new Student("Siemen", 22));

		// Stream operation to group students by age
		Map<Integer, List> studentsByAge = stdList.stream().collect(Collectors.groupingBy(s -> s.age));

		// Print student group one by one
		studentsByAge.forEach((age, s) -> System.out.format("Student Age %s: %s\n", age, s));

		// Compute average age of students
		Double averageAge = stdList.stream().collect(Collectors.averagingInt(s -> s.age));

		System.out.println("Average Age : " + averageAge);

		// Overall age statistics
		IntSummaryStatistics ageSummary = stdList.stream().collect(Collectors.summarizingInt(s -> s.age));

		System.out.println("Age Summary: " + ageSummary);

		// Join by same age
		String age = stdList.stream().filter(s -> s.age <= 22).map(s -> s.name)
				.collect(Collectors.joining(" and ", " ", " are 22 year old or less."));

		System.out.println("Join Person by Age: " + age);

		// Mapping key and values together
		Map<Integer, String> map = stdList.stream()
				.collect(Collectors.toMap(s -> s.age, s -> s.name, (name1, name2) -> name1 + ";" + name2));

		System.out.println("Mapping keys and Values together : " + map);

	}
}

Output

Student Age 21: [Danial]
Student Age 22: [Peter, Siemen]
Student Age 23: [Sara]
Average Age : 22.0
Age Summary: IntSummaryStatistics{count=4, sum=88, min=21, average=22.000000, max=23}
Join Person by Age:  Peter and Danial and Siemen are 22 year old or less.
Mapping keys and Values together : {21=Danial, 22=Peter;Siemen, 23=Sara}

Another more complicated example is to build your own special collector. In the example below we transformed the stream into a single String containing all names in upper letter and separated by comma delimiter. We used all four ingredients such as supplier, accumulator, combiner and finisher.

package java8;

import java.util.ArrayList;
import java.util.StringJoiner;
import java.util.stream.Collector;

public class Student {
	String name;
	int age;

	Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return name;
	}

	public static void main(String[] args) {
		
		// Create an list of student of objects
		ArrayList stdList = new ArrayList();
		stdList.add(new Student("Peter", 22));
		stdList.add(new Student("Sara", 23));
		stdList.add(new Student("Danial", 21));
		stdList.add(new Student("Siemen", 22));

		Collector<Student, StringJoiner, String> stdNameCollector =
			    Collector.of(
			        () -> new StringJoiner(", "),          	// supplier
			        (i, s) -> i.add(s.name.toUpperCase()),  // accumulator
			        (x, y) -> x.merge(y),               	// combiner
			        StringJoiner::toString);                // finisher

			String names = stdList
			    .stream()
			    .collect(stdNameCollector);

			System.out.println(names);  

	}
}

Output

PETER, SARA, DANIAL, SIEMEN

How to use FlatMap Operations?

FlatMap returns a stream which consist of the results, came from replacing each element of current stream with another mapped stream.  Map has a limitation that can map exactly one other object. FlatMap can be used to overcome this kind of shortcomings and one object is transformable to multiple others.

In below example we are using two classes Student and Register. We used stream to instantiate Student and Register objects in the main method of the program. The we created three classes for every single student.

package java8;

import java.util.ArrayList;
import java.util.stream.IntStream;

public class Student {
	public String name;
	int age;

	Student(String name) {
		this.name = name;
	}

	Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return name;
	}

	public static void main(String[] args) {
		ArrayList stClass = new ArrayList<>();

		// create classes
		IntStream.range(1, 4).forEach(i -> stClass.add(new Register("class" + i)));

		// Register students
		stClass.forEach(
				std -> IntStream.range(1, 4).forEach(i -> std.std.add(new Student("Std" + i + " <- " + std.stdClass)))); stClass.stream().flatMap(s -> s.std.stream()).forEach(s1 -> System.out.println(s1.name));

	}
}

class Register {
	String stdClass;
	ArrayList std = new ArrayList<>();

	Register(String stdClass) {
		this.stdClass = stdClass;
	}
}

Output

Std1 <- class1
Std2 <- class1
Std3 <- class1
Std1 <- class2
Std2 <- class2
Std3 <- class2
Std1 <- class3
Std2 <- class3
Std3 <- class3

How reduce works in stream?

The reduction operation combines all elements of the stream into a single result.

package java8;

import java.util.ArrayList;

import java.util.stream.IntStream;

public class Student {
	public String name;
	int age;
Student(String name){
	this.name=name;
}
	Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return name;
	}
	
	public static void main(String[] args) {
		ArrayList std = new ArrayList<>();
		std.add(new Student("A",21));
		
		// create classes
		IntStream
		    .range(1, 5)
		    .forEach(i -> std.add(new Student("s" + i,22)));

		//Return student name with maximum age
		std
	    .stream()
	    .reduce((s1, s2) -> s1.age > s2.age ? s1 : s2)
	    .ifPresent(System.out::println);     
	}
}

Output

s4

The following method accepts both identity value and a BinaryOperator accumulator and aggregate names and ages of all student in stream.

package java8;

import java.util.ArrayList;

import java.util.stream.IntStream;

public class Student {
	public String name;
	int age;

	Student(String name) {
		this.name = name;
	}

	Student(String name, int age) {
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return name;
	}

	public static void main(String[] args) {
		ArrayList std = new ArrayList<>();
		std.add(new Student("A1", 21));

		// create classes
		IntStream.range(1, 5).forEach(i -> std.add(new Student("s" + i, 22)));

		Student result = std.stream().reduce(new Student("", 0), (s1, s2) -> {
			s1.age += s2.age;
			s1.name += s2.name;
			return s1;
		});

		System.out.format("name=%s; age=%s", result.name, result.age);
	}
}

Output

Comments

  1. Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.