The most powerful feature of Java 8
Lambda and Stream are the most unforgettable functions in the technical system of Java 8. The combination of the two operations to achieve a natural match is a little unstoppable.
It is mainly used to supplement the collection class. It is powerful. I believe friends who have used it can obviously feel that they can operate the collection well without using the for loop.
Stream uses an intuitive way similar to querying data from a database with SQL statements to provide a high-level abstraction for the operation and expression of Java collections.
This style regards the set of elements to be processed as a stream, which is transmitted in the pipeline and can be processed on the nodes of the pipeline, such as filtering, sorting, aggregation, etc.
The element flow is processed by intermediate operation in the pipeline, and finally the result of the previous processing is obtained by final operation.
Stream stream
The addition of Stream processing is one of the main new functions in Java 8. To understand these, you need to have a basic working knowledge of Java 8 (lambda expressions, method references).
Stream function introduction
-
First, the Stream stream Stream of Java 8 should be distinguished from the original I / O Stream of Java (e.g. FileInputStream, etc.), which are two things in essence
-
In short, a stream is a wrapper around a data source that enables us to use the data source for operations and makes batch processing convenient and fast.
-
A stream does not store data. In this sense, it is not a data structure, and it will never modify the underlying data source.
-
This new function java.util.stream - supports functional style operations on element streams, such as map reduce conversion on collections.
Stream function definition
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.
Using Stream API can greatly improve the productivity of Java programmers and let programmers write efficient, clean and concise code.
Functional analysis of Stream
- Stream operators can be divided into two types: intermediate operators and termination operators
Operators in process
For data flow, after the operator in the process executes the specified handler, the data flow can still be passed to the next level operator.
- Map (mapto int, mapto long, mapto double): conversion operators, such as a - > b, are provided by default.
- flatmap(flatmapToInt,flatmapToLong,flatmapToDouble) flattening operation, such as flattening int[]{2,3,4} into 2,3,4, that is, from one data to three data. By default, the operator flattening into int,long,double is provided.
- limit current limiting operation. For example, there are 10 data streams. I can use only the first 3.
- Distinct de duplication operation, de duplication of repeated elements, and the equals method is used at the bottom.
- Filter operation to filter unwanted data.
- peek pick out operation. If you want to perform some operations on the data, such as reading, editing and modifying.
- Skip skip operation, skip some elements.
- sorted(unordered) sorting operation is used to sort elements. The premise is to implement the Comparable interface. Of course, you can also customize the comparator.
Process termination operator
After intermediate processing, it's the turn of the termination operator. The termination operator is used to collect or consume data. The data will not flow down here when it comes to the termination operation. The termination operator can only be used once.
- collect collects all data. This operation is very important. The official Collectors provide many Collectors. It can be said that the core of Stream lies in Collectors.
- Count statistics operation to count the final number of data.
- findFirst and findAny search operations. The return type of the first and any search operations is Optional.
- For noneMatch, allMatch and anyMatch matching operations, whether there are qualified elements in the data stream, the return value is bool value.
- For min and max operations, you need to customize the comparator to return the maximum and minimum values in the data stream.
- Reduce protocol operation, which regulates the value of the whole data stream into one value. The bottom layer of count, min and max is to use reduce.
- forEach and forEachOrdered traversal operations, where the final data is consumed.
- The toArray array operation converts the elements of the data stream into an array.
Note: there are 8 kinds of intermediate operators (parallel and sequential are excluded. These two operations do not involve processing of data flow)
API analysis of Stream
Creation of Stream
- Create a stream from a collection through the java.util.Collection.stream() method
List<String> list = Arrays.asList("a", "b", "c"); // Create a sequential flow Stream<String> stream = list.stream(); // Create a parallel stream Stream<String> parallelStream = list.parallelStream();
- Use the java.util.Arrays.stream(T[] array) method to create a stream from an array
int[] array={1,3,5,6,8}; IntStream stream = Arrays.stream(array);
- Static methods using Stream: of(), iterate(), generate()
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6); Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4); stream2.forEach(System.out::println); // 0 3 6 9 Stream<Double> stream3 = Stream.generate(Math::random).limit(3); stream3.forEach(System.out::println);
Simple distinction between stream and parallel stream:
For odd numbers in the filter set, the two processes are different:
-
Stream is a sequential stream, and the main thread performs operations on the stream in sequence;
-
Parallel stream is a parallel stream. It operates internally in the form of multi-threaded parallel execution, but the premise is that there is no sequence requirement for data processing in the stream.
Traverse / match (foreach/find/match)
Stream also supports traversal and matching elements of similar collections, but the elements in stream exist as Optional types. The traversal and matching of stream is very simple.
List<Integer> list = Arrays.asList(7, 6, 9, 3, 8, 2, 1); // Traverse the elements whose output meets the criteria list.stream().filter(x -> x > 6).forEach(System.out::println); // Match first Optional<Integer> findFirst = list.stream().filter(x -> x > 6).findFirst(); // Match any (for parallel streams) Optional<Integer> findAny = list.parallelStream().filter(x -> x > 6).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);
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. Filter out the elements greater than 7 in the Integer set and print them
public class StreamTest { public static void main(String[] args) { List<Integer> list = Arrays.asList(6, 7, 3, 8, 1, 2, 9); Stream<Integer> stream = list.stream(); stream.filter(x -> x > 7).forEach(System.out::println); } }
Aggregation (max/min/count)
You must be familiar with the words max, min and count. Yes, we often use them for data statistics in mysql. These concepts and usages are also introduced into Java stream, which greatly facilitates our data statistics of sets and arrays.
Gets the longest element in the String collection.
public class StreamTest { public static void main(String[] args) { List<String> list = Arrays.asList("adnm", "admmt", "pot", "xbangd", "weoujgsd"); Optional<String> max = list.stream().max(Comparator.comparing(String::length)); System.out.println("Longest string:" + max.get()); } }
Output results:
Longest string: weoujgsd
Gets the maximum value in the Integer collection.
public class StreamTest { public static void main(String[] args) { 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()); } }
Calculates the number of elements greater than 5 in the Integer set.
public class StreamTest { public static void main(String[] args) { List<Integer> list = Arrays.asList(7, 6, 4, 8, 2, 11, 9); long count = list.stream().filter(x -> x > 5).count(); System.out.println("list Number of elements greater than 6 in:" + count); } }
Mapping (map/flatMap)
Mapping 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.
All elements of the English string array are changed to uppercase.
String[] strArr = { "abcd", "bcdd", "defde", "fTr" }; List<String> strList = Arrays.stream(strArr).map(String::toUpperCase).collect(Collectors.toList()); System.out.println("Capitalize each element:" + strList);
Integer array + 3 per element
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("Each element+3: " + intListNew);
Combines 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());
Merge operation (reduce)
Merging, 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(Integer::sum); // 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);
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
Case: count the number of employees, average salary, total salary and maximum salary.
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")); // Total long count = personList.size(); // Average wage Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary)); // Seek maximum wage Optional<Integer> max = personList.stream().map(Person::getSalary).max(Integer::compare); // Sum of wages int sum = personList.stream().mapToInt(Person::getSalary).sum(); // One time statistics of all information DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary)); System.out.println("Total number of employees:" + count); System.out.println("Average salary of employees:" + average); System.out.println("Maximum salary of employees:" + max.get()); System.out.println("Total employee salary:" + sum); System.out.println("Employee salary statistics:" + collect);
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. Case: divide employees into two parts according to whether their salary is higher than 8000; Group employees by gender and region
List<Person> personList = new ArrayList<Person>(); personList.add(new Person("Tom", 8900, 23, "male", "Washington")); personList.add(new Person("Jack", 7000, 25, "male", "Washington")); personList.add(new Person("Lily", 7800, 21, "female", "New York")); personList.add(new Person("Anni", 8200, 24, "female", "New York")); // Group employees by salary above 8000 Map<Boolean, List<Person>> part = personList.stream().collect(Collectors.partitioningBy(x -> x.getSalary() > 8000)); // Group employees by gender Map<String, List<Person>> group = personList.stream().collect(Collectors.groupingBy(Person::getSex)); // Employees are grouped first by gender and then by region Map<String, Map<String, List<Person>>> group2 = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.groupingBy(Person::getArea)));
Joining
joining can connect the elements in the stream into a string with a specific connector (or directly if not).
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")); String names = personList.stream().map(Person::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);
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
Case: sort 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());
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);
Actual case operation
Traversal set
In daily development, we often need to traverse the elements in the set object. For example, we will traverse the elements in the following way, and then filter out the set of a certain field. After using Stream programming, we only need one line of code to realize it:
/** * jdk8 Gets the user ID collection from the collection object * @param userList * @return */ public List<Long> getUserIds(List<User> userList){ List<Long> userIds = userList.stream().map(User::getUserId).collect(Collectors.toList()); return userIds; }
Filter elements
Filtering elements are often encountered in daily development. For example, in jdk8, using the Stream api, we only need to filter the required data through the filter method to filter the data whose user ID is not empty.
/** * jdk8 Filter out the data whose user ID is not empty from the collection object * @param userList * @return */ public List<Long> getUserIds8(List<User> userList){ List<Long> userIds = userList.stream().filter(item -> item.getUserId() != null).map(User::getUserId).collect(Collectors.toList()); return userIds; }
Delete duplicate content
If you want to exclude duplicate data from the returned collection content, the operation is also very simple. You can use Collectors.toSet() when merging!
/** * jdk8 Filter out the data whose user ID is not empty from the collection object and de duplicate it * @param userList * @return */ public Set<Long> getUserIds(List<User> userList){ Set<Long> userIds = userList.stream().filter(item -> item.getUserId() != null).map(User::getUserId).collect(Collectors.toSet()); return userIds; }
Data type conversion
In the actual development process, inconsistent data type definitions often occur. For example, some systems use String acceptance and some use Long. For this scenario, we need to convert it, and the operation is very simple
/** * jdk8 Convert Long type data to String type * @param userIds * @return */ public List<String> getUserIds10(List<Long> userIds){ List<String> userIdStrs = userIds.stream().map(x -> x.toString()).collect(Collectors.toList()); return userIdStrs; }
Array to set
We will also encounter that the front end passes us an array, but we need to convert it into a collection. The operation using stream api is also very simple!
public static void main(String[] args) { //Create an array of strings String[] strArray = new String[]{"a","b","c"}; //The converted List belongs to java.util.ArrayList and can be added, deleted and queried normally List<String> strList = Stream.of(strArray).collect(Collectors.toList()); }
Set to Map operation
In the actual development process, another operation that is most frequently used is to take a primary key field in the set element as the key and the element as the value to realize the demand of converting the set to map. This demand is used very much in data assembly, especially in companies that prohibit table linked sql query, the assembly of view data can only be realized at the code level.
For example, in the following code, the role table is associated with the role group ID information. When querying the role information, you need to display and process the role group name, and use the map method to match, which will be very efficient.
Actual code case sharing
//Role group ID collection Set<Long> roleGroupIds = new HashSet<>(); //Query all role information List<RoleInfo> dbList = roleInfoMapper.findByPage(request); for (RoleInfo source : dbList) { roleGroupIds.add(source.getRoleGroupId()); RoleInfoDto result = new RoleInfoDto(); BeanUtils.copyProperties(source, result); resultList.add(result); } //Query role group information if (CollectionUtils.isNotEmpty(roleGroupIds)) { List<RoleGroupInfo> roleGroupInfoList = roleGroupInfoMapper.selectByIds(new ArrayList<>(roleGroupIds)); if (CollectionUtils.isNotEmpty(roleGroupInfoList)) { //Convert the List into a Map, where the id primary key is used as the key and the object is used as the value Map<Long, RoleGroupInfo> sourceMap = new HashMap<>(); for (RoleGroupInfo roleGroupInfo : roleGroupInfoList) { sourceMap.put(roleGroupInfo.getId(), roleGroupInfo); } //Encapsulate role group name for (RoleInfoDto result : resultList) { if (sourceMap.containsKey(result.getRoleGroupId())) { result.setRoleGroupName(sourceMap.get(result.getRoleGroupId()).getName()); } } } }
Set to map (no grouping)
In jdk8, using the stream api, we only need one line of code to realize it. Jdk8 converts the collection into a Map, in which the user ID is used as the primary key. If the collection object has duplicate keys, the first matching key is the main key
public Map<Long, User> getMap(List<User> userList){ Map<Long, User> userMap = userList.stream().collect(Collectors.toMap(User::getUserId, v -> v, (k1,k2) -> k1)); return userMap; }
Open the source code of Collectors.toMap method and let's see what it is.
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) { return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new); }
As can be seen from the parameter table:
- The first parameter: indicates key
- The second parameter: indicates value
- The third parameter: indicates a rule
Collectors. Tomap (user:: getuserid, V - > V, (K1, K2) - > K1) above means:
- The content of userId is used as the key
- V - > V means that the element user is taken as value.
- (K1, K2) - > K1 indicates that if the same key exists, the first matching element is taken as the content.
Set to map (grouping)
In actual operation, there are some scenarios where we need to add the same key to a collection instead of overwriting. What should we do? In jdk8, we only need one line of code to implement the stream api.
/** * jdk8 Convert the set into a Map, and add the same key to a set to realize grouping * @param userList * @return */ public Map<Long, List<User>> getMapGroup(List<User> userList){ Map<Long, List<User>> userMap = userList.stream().collect(Collectors.groupingBy(User::getUserId)); return userMap; }
paging operation
The strength of stream api is not only to perform various combination operations on collections, but also to support paging operations.
For example, sort the following arrays from small to large. After sorting, query 10 data from row 1. The operations are as follows:
//Data to query List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5,10, 6, 20, 30, 40, 50, 60, 100); List<Integer> dataList= numbers.stream().sorted((x, y) -> x.compareTo(y)).skip(0).limit(10).collect(Collectors.toList()); System.out.println(dataList.toString());
The skip parameter indicates the row number, and the limit indicates the number of queries, similar to the page capacity!
Find and match operations
The stream api also supports finding collections and regular matching patterns.
Allmatch (check to see if all elements match)
- Are all elements greater than 2
List<Integer> list = Arrays.asList(10, 5, 7, 3); boolean allMatch = list.stream() .allMatch(x -> x > 2); System.out.println(allMatch); findFirst(Returns the first element)
- Get the first element
List<Integer> list = Arrays.asList(10, 5, 7, 3); Optional<Integer> first = list.stream() .findFirst(); Integer val = first.get(); System.out.println(val);//Output 10
- (you can combine the elements in the flow repeatedly to get a value)
List<Integer> list = Arrays.asList(10, 5, 7, 3); Integer result = list.stream() .reduce(2, Integer::sum); System.out.println(result);//Output 27, which is actually equivalent to 2 + 10 + 5 + 7 + 3, is an accumulation
There are many operation methods supported by the stream api. Only several types are listed here. For details, please refer to the interface documentation on the official website!