Filtering, reduction, grouping and aggregation of Java8 Stream

Keywords: Java

1. Stream overview

Java 8 is a revolutionary version. The new Stream and Optional in this version, together with Lambda in the contract version, provide great convenience for us to operate the Collection.
Stream regards the set of elements to be processed as a stream. In the process of streaming, the Stream API is used to operate the elements in the stream, such as filtering, sorting, aggregation, etc.

The Optional class is an interesting feature introduced in Java 8. The main problem solved by the Optional class is the notorious null pointer exception.
In essence, this is a wrapper class with Optional values, which means that the Optional class can contain objects or be empty.

This article only talks about Stream, and Optional will write a separate article.

Stream has several characteristics:

  1. stream does not store data, but calculates the data according to specific rules, and generally outputs the results.
  2. stream does not change the data source and usually produces a new set or a value.
  3. stream has the characteristic of delayed execution, and the intermediate operation will be executed only when the terminal operation is called.

2. How to create a Stream

2.1. Create by collection

//First kind
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
//Second
//List<String> list = Arrays.asList("a", "b", "c");
// Sequential flow
Stream<String> stream = list.stream();
// Parallel flow, the first way
Stream<String> parallelStream = list.parallelStream();
//The second way is to create parallel flow and convert sequential flow into parallel flow
Stream<String> parallelStream = list.stream().parallel();

Distinction between stream and parallelStream:
Stream is a sequential stream, in which the main thread performs operations in sequence, while parallel stream is a parallel stream, which internally performs operations in the form of multi-threaded parallel execution, provided that there is no sequence requirement for data processing in the stream.

2.2. Create by array

String[] array={"a","b"};
Stream<String> stream = Arrays.stream(array);

2.3. Static methods using Stream: of(), iterate(), generate()

int[] array={1,2,3,4};
//public static<T> Stream<T> of(T... values) 
Stream<String> stream = Stream.of(array);
//Iterate out 3 numbers according to the rules
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x * 2).limit(3);
stream2.forEach(System.out::println);
//Randomly generate 3 numbers
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);

3. API usage of Stream

Test entity:

@data
public class Staff{
	// full name
	private String name; 
	// salary
	private int salary;
	// Age
	private int age;
	private String sex;
	private String area;  // region
}

3.1. foreach/find/match

List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1);

        // Traverse the elements whose output meets the criteria
        list.stream().forEach(System.out::println);
        // Match first
        Optional<Integer> findFirst = list.stream().findFirst();
        // Match any (for parallel streams)
        Optional<Integer> findAny = list.parallelStream().findAny();
        // Whether to include elements that meet specific conditions
        boolean anyMatch = list.stream().anyMatch(x -> x < 6);
        System.out.println("Match first value:" + findFirst.get());
        System.out.println("Match any value:" + findAny.get());
        System.out.println("Is there a value greater than 6:" + anyMatch);

3.2. filter

Filtering is the operation of verifying the elements in the flow according to certain rules and extracting the qualified elements into the new flow.

//Demand: select employees with a salary higher than 8000 and form a new collection
List<Staff> staffList = new ArrayList<Staff>();
		staffList.add(new Staff("Tom", 8900, 23, "male", "New York"));
		staffList.add(new Staff("Jack", 7000, 25, "male", "Washington"));
		staffList.add(new Staff("Lily", 7800, 21, "female", "Washington"));
		staffList.add(new Staff("Anni", 8200, 24, "female", "New York"));
		staffList.add(new Staff("Owen", 9500, 25, "male", "New York"));
		staffList.add(new Staff("Alisa", 7900, 26, "female", "New York"));

		List<String> fiterList = staffList.stream().filter(x -> x.getSalary() > 8000).map(Staff::getName)
				.collect(Collectors.toList());
		System.out.print("Name of employees above 8000:" + fiterList);

3.3 polymerization (max/min/count)

Java stream also introduces the usage of max, min and count, which greatly facilitates our data statistics of sets and arrays.

//Demand: get the highest salary of employees
List<Staff> staffList= new ArrayList<Staff>();
		staffList.add(new Staff("Tom", 8900, 23, "male", "New York"));
		staffList.add(new Staff("Jack", 7000, 25, "male", "Washington"));
		staffList.add(new Staff("Lily", 7800, 21, "female", "Washington"));
		staffList.add(new Staff("Anni", 8200, 24, "female", "New York"));
		staffList.add(new Staff("Owen", 9500, 25, "male", "New York"));
		staffList.add(new Staff("Alisa", 7900, 26, "female", "New York"));

		Optional<Person> max = staffList.stream().max(Comparator.comparingInt(Staff::getSalary));
		System.out.println("Maximum employee salary:" + max.get().getSalary());
//Requirement: gets the maximum value in the Integer set
List<Integer> list = Arrays.asList(7, 6, 9, 4, 11, 6);

		// Natural sorting
		Optional<Integer> max = list.stream().max(Integer::compareTo);
		// Custom sorting
		Optional<Integer> max2 = list.stream().max(new Comparator<Integer>() {
			@Override
			public int compare(Integer o1, Integer o2) {
				return o1.compareTo(o2);
			}
		});
		System.out.println("Maximum natural sort:" + max.get());
		System.out.println("Maximum value of custom sort:" + max2.get());
//Requirement: get the shortest element in the String set
List<String> list = Arrays.asList("adnm", "admmt", "pot", "xbangd", "weoujgsd");

		Optional<String> min = list.stream().min(Comparator.comparing(String::length));
		System.out.println("Longest string:" + min.get());
//Requirement: calculate the number of elements greater than 6 in the Integer set
List<Integer> list = Arrays.asList(7, 6, 4, 8, 2, 11, 9);

		long count = list.stream().filter(x -> x > 6).count();
		System.out.println("list Number of elements greater than 6 in:" + count);

3.4 mapping (map/flatMap)

Mapping, you can map the elements of one stream to another stream according to certain mapping rules. It is divided into map and flatMap:

  • map: takes a function as an argument, which is applied to each element and mapped to a new element.
  • flatMap: take a function as a parameter, replace each value in the stream with another stream, and then connect all streams into one stream.
//Requirement: all elements of English string array are changed to uppercase. Each element of integer array + 3
String[] strArr = { "abcd", "bcdd", "defde", "fTr" };
		List<String> strList = Arrays.stream(strArr).map(String::toUpperCase).collect(Collectors.toList());

		List<Integer> intList = Arrays.asList(1, 3, 5, 7, 9, 11);
		List<Integer> intListNew = intList.stream().map(x -> x + 3).collect(Collectors.toList());

		System.out.println("Capitalize each element:" + strList);
		System.out.println("Each element+3: " + intListNew);
//Requirement: combine two character arrays into a new character array
List<String> list = Arrays.asList("m,k,l,a", "1,3,5,7");
		List<String> listNew = list.stream().flatMap(s -> {
			// Convert each element into a stream
			String[] split = s.split(",");
			Stream<String> s2 = Arrays.stream(split);
			return s2;
		}).collect(Collectors.toList());

		System.out.println("Collection before processing:" + list);
		System.out.println("Processed collection:" + listNew);

3.5. Reduce

Reduction, also known as reduction, as the name suggests, is to reduce a stream to a value, which can realize the operations of summation, product and maximum of a set.

List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4);
		// Summation method 1
		Optional<Integer> sum = list.stream().reduce((x, y) -> x + y);
		// Summation method 2
		Optional<Integer> sum2 = list.stream().reduce(Integer::sum);
		// Summation method 3
		Integer sum3 = list.stream().reduce(0, Integer::sum);
		
		// Product
		Optional<Integer> product = list.stream().reduce((x, y) -> x * y);

		// Maximum method 1
		Optional<Integer> max = list.stream().reduce((x, y) -> x > y ? x : y);
		// Find the maximum 2
		Integer max2 = list.stream().reduce(1, Integer::max);

		System.out.println("list Summation:" + sum.get() + "," + sum2.get() + "," + sum3);
		System.out.println("list Quadrature:" + product.get());
		System.out.println("list Summation:" + max.get() + "," + max2);

3.6. Collect

Collection, collection, can be said to be the most diverse part with the most functions. Literally, it means collecting a stream, which can eventually be collected into a value or a new collection.
collect mainly relies on the built-in static methods of the java.util.stream.Collectors class.

3.6.1. Collection (toList/toSet/toMap)

Because the stream does not store data, after the data in the stream is processed, the data in the stream needs to be re collected into a new set. toList, toSet and toMap are commonly used, and toCollection, tocurrentmap and other complex usages are also used.

List<Integer> list = Arrays.asList(1, 6, 3, 4, 6, 7, 9, 6, 20);
		List<Integer> listNew = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toList());
		Set<Integer> set = list.stream().filter(x -> x % 2 == 0).collect(Collectors.toSet());

		List<Staff> staffList = new ArrayList<Staff>();
		staffList.add(new Staff("Tom", 8900, 23, "male", "New York"));
		staffList.add(new Staff("Jack", 7000, 25, "male", "Washington"));
		staffList.add(new Staff("Lily", 7800, 21, "female", "Washington"));
		staffList.add(new Staff("Anni", 8200, 24, "female", "New York"));
		
		Map<?, Staff> map = staffList.stream().filter(p -> p.getSalary() > 8000)
				.collect(Collectors.toMap(Staff::getName, p -> p));
		System.out.println("toList:" + listNew);
		System.out.println("toSet:" + set);
		System.out.println("toMap:" + map);

3.6.2 Statistics (count/averaging)

Collectors provide a series of static methods for data statistics:

  • Count: count
  • Average value: averagingInt, averagingLong, averagingDouble
  • Max value: maxBy, minBy
  • Summation: summerint, summerlong, summerdouble
  • Statistics of all the above: summarizingInt, summarizingLong, summarizingDouble
List<Staff> staffList= new ArrayList<Staff>();
		staffList.add(new Staff("Tom", 8900, 23, "male", "New York"));
		staffList.add(new Staff("Jack", 7000, 25, "male", "Washington"));
		staffList.add(new Staff("Lily", 7800, 21, "female", "Washington"));

		// Total
		Long count = staffList.stream().collect(Collectors.counting());
		// Average wage
		Double average = staffList.stream().collect(Collectors.averagingDouble(Staff::getSalary));
		// Seek maximum wage
		Optional<Integer> max = staffList.stream().map(Staff::getSalary).collect(Collectors.maxBy(Integer::compare));
		// Sum of wages
		Integer sum = staffList.stream().collect(Collectors.summingInt(Staff::getSalary));
		// One time statistics of all information
		DoubleSummaryStatistics collect = staffList.stream().collect(Collectors.summarizingDouble(Staff::getSalary));

		System.out.println("Total number of employees:" + count);
		System.out.println("Average salary of employees:" + average);
		System.out.println("Total employee salary:" + sum);
		System.out.println("Employee salary statistics:" + collect);

Operation results:

Total number of employees: 3
 Average salary of employees: 7900.0
 Total employee salary: 23700
 Employee salary statistics: DoubleSummaryStatistics{count=3, sum=23700.000000,min=7000.000000, average=7900.000000, max=8900.000000}

3.6.3. Grouping (partitioningBy/groupingBy)

  • Partition: divide the stream into two maps according to conditions. For example, employees are divided into two parts according to whether their salary is higher than 8000.
  • Grouping: divide the set into multiple maps, such as grouping employees by gender. There are single level grouping and multi-level grouping.
//Demand: divide employees into two parts according to whether their salary is higher than 8000; Group employees by gender and region
List<Staff> staffList= new ArrayList<Staff>();
		staffList.add(new Staff("Tom", 8900, "male", "New York"));
		staffList.add(new Staff("Jack", 7000, "male", "Washington"));
		staffList.add(new Staff("Lily", 7800, "female", "Washington"));
		staffList.add(new Staff("Anni", 8200, "female", "New York"));
		staffList.add(new Staff("Owen", 9500, "male", "New York"));
		staffList.add(new Staff("Alisa", 7900, "female", "New York"));

		// Group employees by salary above 8000
        Map<Boolean, List<Staff>> part = staffList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000));
        // Group employees by gender
        Map<String, List<Staff>> group = staffList.stream().collect(Collectors.groupingBy(Staff::getSex));
        // Employees are grouped first by gender and then by region
        Map<String, Map<String, List<Staff>>> group2 = staffList.stream().collect(Collectors.groupingBy(Staff::getSex, Collectors.groupingBy(Staff::getArea)));
        System.out.println("Grouping of employees by salary greater than 8000:" + part);
        System.out.println("Employees are grouped by gender:" + group);
        System.out.println("Employees by gender and region:" + group2);

Operation results:

Grouping of employees by salary greater than 8000:{false=[mutest.Person@2d98a335, mutest.Person@16b98e56, mutest.Person@7ef20235], true=[mutest.Person@27d6c5e0, mutest.Person@4f3f5b24, mutest.Person@15aeb7ab]}
Employees are grouped by gender:{female=[mutest.Person@16b98e56, mutest.Person@4f3f5b24, mutest.Person@7ef20235], male=[mutest.Person@27d6c5e0, mutest.Person@2d98a335, mutest.Person@15aeb7ab]}
Employees by gender and region:{female={New York=[mutest.Person@4f3f5b24, mutest.Person@7ef20235], Washington=[mutest.Person@16b98e56]}, male={New York=[mutest.Person@27d6c5e0, mutest.Person@15aeb7ab], Washington=[mutest.Person@2d98a335]}}

3.6.4 joining

joining can connect the elements in the stream into a string with a specific connector (or directly if not).

List<Staff> staffList= new ArrayList<Staff>();
		staffList.add(new Staff("Tom", 8900, 23, "male", "New York"));
		staffList.add(new Staff("Jack", 7000, 25, "male", "Washington"));
		staffList.add(new Staff("Lily", 7800, 21, "female", "Washington"));

		String names = staffList.stream().map(p -> p.getName()).collect(Collectors.joining(","));
		System.out.println("Names of all employees:" + names);
		List<String> list = Arrays.asList("A", "B", "C");
		String string = list.stream().collect(Collectors.joining("-"));
		System.out.println("Spliced string:" + string);

Operation results:

Names of all employees: Tom,Jack,Lily
 Spliced string: A-B-C

3.6.5 reducing

Compared with the reduce method of stream itself, the reducing method provided by the Collectors class adds support for custom reduction.

List<Person> personList = new ArrayList<Person>();
		personList.add(new Person("Tom", 8900, 23, "male", "New York"));
		personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
		personList.add(new Person("Lily", 7800, 21, "female", "Washington"));

		// Sum of salary of each employee after deducting the threshold
		Integer sum = personList.stream().collect(Collectors.reducing(0, Person::getSalary, (i, j) -> (i + j - 5000)));
		System.out.println("Total tax deduction salary of employees:" + sum);

		// reduce of stream
		Optional<Integer> sum2 = personList.stream().map(Person::getSalary).reduce(Integer::sum);
		System.out.println("Total employee salary:" + sum2.get());

Operation results:

Total tax deduction salary of employees: 8700
 Total employee salary: 23700

3.7. Sorted

sorted, intermediate operation. There are two sorts:

  • sorted(): sort naturally. Elements in the stream need to implement the Comparable interface
  • sorted(Comparator com): Comparator sorter custom sorting
//Sort the employees by salary from high to low (if the salary is the same, then by age)
List<Person> personList = new ArrayList<Person>();

		personList.add(new Person("Sherry", 9000, 24, "female", "New York"));
		personList.add(new Person("Tom", 8900, 22, "male", "Washington"));
		personList.add(new Person("Jack", 9000, 25, "male", "Washington"));
		personList.add(new Person("Lily", 8800, 26, "male", "New York"));
		personList.add(new Person("Alisa", 9000, 26, "female", "New York"));

		// Sort by salary ascending (natural sort)
		List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
				.collect(Collectors.toList());
		// Sort by salary in reverse order
		List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
				.map(Person::getName).collect(Collectors.toList());
		// Sort by salary and then by age
		List<String> newList3 = personList.stream()
				.sorted(Comparator.comparing(Person::getSalary).thenComparing(Person::getAge)).map(Person::getName)
				.collect(Collectors.toList());
		// Sort by salary first and then by age (descending order)
		List<String> newList4 = personList.stream().sorted((p1, p2) -> {
			if (p1.getSalary() == p2.getSalary()) {
				return p2.getAge() - p1.getAge();
			} else {
				return p2.getSalary() - p1.getSalary();
			}
		}).map(Person::getName).collect(Collectors.toList());

		System.out.println("Sort by salary in ascending order:" + newList);
		System.out.println("Sort by salary in descending order:" + newList2);
		System.out.println("Sort by salary first and then by age in ascending order:" + newList3);
		System.out.println("Sort by salary first and then by age in descending order:" + newList4);

Operation results:

Sort by salary in ascending order:[Lily, Tom, Sherry, Jack, Alisa]
Sort by salary in descending order:[Sherry, Jack, Alisa, Tom, Lily]
Sort by salary first and then by age in ascending order:[Lily, Tom, Sherry, Jack, Alisa]
Sort by salary first and then by age in descending order:[Alisa, Jack, Sherry, Tom, Lily]

3.8 extraction / combination

Streams can also be merged, de duplicated, restricted, and skipped.

String[] arr1 = { "a", "b", "c", "d" };
		String[] arr2 = { "d", "e", "f", "g" };

		Stream<String> stream1 = Stream.of(arr1);
		Stream<String> stream2 = Stream.of(arr2);
		// concat: merge two streams distinct: de duplication
		List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
		// Limit: limit the first n data obtained from the stream
		List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
		// Skip: skip the first n data
		List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());

		System.out.println("Stream merge:" + newList);
		System.out.println("limit: " + collect);
		System.out.println("skip: " + collect2);

Operation results:

Stream merge:[a, b, c, d, e, f, g]
limit: [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
skip: [3, 5, 7, 9, 11]

Posted by Ravrflavr on Mon, 11 Oct 2021 16:10:46 -0700