There are two types of operations for streams:
- Intermediate: A flow can be followed by zero or more Intermediate operations. The main goal is to open streams, do some sort of data mapping/filtering, and then return a new stream for the next operation to use. These operations are lazy, that is, they are called to the method without actually starting the flow.
Map (mapToInt, flatMap, etc.), filter, distinct, sorted, peek, LIMIT, skip, parallel, sequential, and unordered
- Terminal: A stream can have only one Terminal operation. When this operation is performed, the stream is “light” and can no longer be manipulated. So this must be the last operation of the stream. The execution of the Terminal operation will actually start traversing the stream and will produce a result, or a side effect
ForEach, forEachOrdered, toArray, Reduce, Collect, min, Max, Count, anyMatch, allMatch, noneMatch, findFirst, findAny, forEach ordered, toArray, Reduce, Collect, Min, Max, Count, anyMatch, allMatch, noneMatch, findFirst, findAny, iterator
- Short-circuiting: Returns a finite amount of data for an infinite and infinite set
AnyMatch, allMatch, noneMatch, findFirst, findAny, LIMIT
Filter
Object matching filtering
methods | schematic |
---|---|
filter | Return a stream containing all matched objects by passing an expected match as an argument. |
distinct | Returns a stream containing unique elements (uniqueness depends on how element equality is implemented). |
limit | Returns a stream with a specified upper bound |
skip | Returns a stream discarding the first n elements |
List expensiveInvoices
= invoices.stream()
.filter(inv -> inv.getAmount() > 10_000)
.limit(5)
.collect(Collectors.toList());
Copy the code
Matching:
A match is a common data processing pattern that determines whether a given attribute is matched. The expression returns a Boolean at the end
methods | schematic |
---|---|
allMacth | Each of the stream objects should be matched to |
anyMatch | The stream object is interrupted if there is a match |
noneMatch | The flow object has no matching object |
boolean expensive =
invoices.stream()
.allMatch(inv -> inv.getAmount() > 1 _000);
Copy the code
Finding
The: stream interface also provides options like findFirst and findAny to pull arbitrary elements from the stream. They can be connected to methods like filter. Both findFirst and findAny return an optional object. In the serial stream, both return the first object. In the parallel stream, findAny returns the data that was processed fastest by the first thread
methods | schematic |
---|---|
findFirst | Returns the first matched object |
findAny | Returns the first matched object |
Optional =
invoices.stream()
.filter(inv ->
inv.getCustomer() == Customer.ORACLE)
.findAny();
Copy the code
Mapping
: Streams support mapping methods, passing a function object as a method to convert elements in the stream to another type. This approach applies to a single element and maps it to a new element.
methods | schematic |
---|---|
map | Applies to a single element to map it to a new element. |
List ids
= invoices.stream()
.map(Invoice::getId)
.collect(Collectors.toList());
Copy the code
Reducing
methods | schematic |
---|---|
reduce(BinaryOperator) | One function is primarily used to maximize the sum. |
int product = numbers.stream().reduce(1, (a, b) -> a * b);
int max = numbers.stream().reduce(Integer.MIN_VALUE,
Integer::max);
Copy the code
The forEach, peek
ForEach Terminal operations no longer return stream objects. ForEach loops cannot modify the value of local variables they contain. ForEach loops cannot prematurely end with keywords such as break/return. Peek can re-encapsulate a loop object and return a new one
sorted
Instead of sorting an array, you can use maps, filter, LIMIT, SKIP, or even distinct to reduce the number of elements in the Stream first, and then sort the Stream. This will help the program to significantly shorten the execution time.
List<Person> persons = new ArrayList();
for (int i = 1; i <= 5; i++) {
Person person = new Person(i, "name" + i);
persons.add(person);
}
List<Person> personList2 = persons.stream().limit(2).sorted((p1, p2) -> p1.getName().compareTo(p2.getName())).collect(Collectors.toList());
System.out.println(personList2);
Copy the code
min/max/distinct(Terminal)
Get the minimum value, maximum value and de-repetition of the final calculation
Collectors
All the methods you’ve seen so far have been to return another stream or a value of type Boolean,int, or an optional object. In contrast, the collect method is a closing operation that aggregates all elements in the stream into a summary result. Pass to collect method parameter is a Java util. Stream. The Collector type of object. The Collector object actually defines a method for aggregating elements from the flow into the final result. Initially, the factory method yles.tolist () was used to return a Collector object that described how to turn the flow into a List. Many similar Collectors have been built into the Collectors class. For example, you can group invoices by consumer using the oughter.groupingby method
Map<Customer, List> customerToInvoices
= invoices.stream().collect(Collectors.group
ingBy(Invoice::getCustomer));
Copy the code
groupingBy/partitioningBy
Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier()).
limit(100).
collect(Collectors.groupingBy(Person::getAge));
Iterator it = personGroups.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();
System.out.println("Age " + persons.getKey() + "=" + persons.getValue().size());
}
Copy the code
After using the condition “age < 18” for grouping, you can see that minors under 18 are one group and adults are another. PartitioningBy is a special type of groupingBy, which constructs the returned data structure according to whether the condition tests two results, get(true) and get(false) can be all the element objects.
The input and output processed by map correspond to each other
methods | schematic |
---|---|
mapToInt | The body of the function returns an int |
mapToDouble | The body of the function returns type Double. |
mapToLong | The body of the function returns a value of type Long. |
The flatMap processes input and outPut in a one-to-many relationship
methods | schematic |
---|---|
flatMapToInt | The body of the function returns an int |
flatMapDouble | The body of the function returns type Double. |
flatMapLong | The body of the function returns a value of type Long. |
Stream<List<Integer>> inputStream = Stream.of(
Arrays.asList(1),
Arrays.asList(2.3),
Arrays.asList(4.5.6)); Stream<Integer> outputStream = inputStream. flatMap((childList) -> childList.stream());Copy the code
Parallel Streams
The Stream API supports convenient data parallelism. In other words, you can explicitly let the stream pipe run in parallel without worrying about the underlying implementation. Behind the scenes, the Stream API uses the Fork/Join framework to take full advantage of your machine’s multi-core architecture. All you need to do is replace the stream() method with the parallelStream() method. Not all are in parallel, as follows
- The internal implementation of Parallel Streams depends on how easy it is to divide data structures into pieces that can be used by different threads. Data structures like arrays are easy to partition, while data structures like lists or files are hard to partition
- The higher the cost of a single element in the computation flow, the more sense it makes to apply parallelism.
- Use raw data types if possible, which use less memory and have a higher cache hit ratio.
- The larger the data volume of the elements in the stream, the better, because the cost of parallelization is spread across all the elements, and the time savings of parallelization are relatively greater. Of course, this is also related to the cost of the individual element calculation.
- In general, the more cores, the better.