Java8 Stream improves coding efficiency and leaves work early

Posted by bobthebuilder on Wed, 09 Feb 2022 18:39:29 +0100

Collection data is frequently manipulated in programming. Using Stream in Java8 to process collections, combined with Lambda functional programming, can greatly simplify code. Reasonable use of Stream can improve code readability. On the other hand, since the advent of Java8, the Stream API has been tested by numerous projects, its stability and performance need not be said. There are many related performance test cases on the web to refer to. If someone says to you: Lambda is not readable, high maintenance costs and some other issues, you can rest assured, please take a look at the last point of attention

1. Stream Creation

There are many ways to create Stream. Here are several common ways to create Stream. Lists below use the API of google guava to code directly:

// Mode 1: Stream.of and other static methods, commonly used by tests
Stream<String> stream1 = Stream.of("A", "B");
// Mode 2: Collection mode, common such as List, Set
Stream<String> stream2 = Lists.newArrayList("A", "B").stream();
Stream<String> stream3 = Sets.newHashSet("A", "B").stream();
// Mode 3: Array mode
Stream<String> stream4 = Arrays.stream(new String[]{"A", "B"});
// Mode 4: Create through API interface, file API, etc.
Stream<String> stream5 = Files.lines(Paths.get("/file.text"));
// Mode 5: Create Stream corresponding to the basic data type, such as IntStream, LongStream, DoubleStream
IntStream stream6 = Arrays.stream(new int[] { 1, 2, 3 });
// Mode 6: Through Stream.builder creation
Stream<Object> stream7 = Stream.builder().add("A").build();

Modes 2 and 3 above are commonly used. Among them, mode 3 can also use parallelStream to create parallel streams. Other methods can be converted to parallel streams by parallel method to improve data processing efficiency when there is a large amount of data, as follows:

// Create directly using parallelStream
Stream<String> stream1 = Lists.newArrayList("A", "B").parallelStream();
// Converting ordinary streams to parallel streams using parallel
Stream<String> stream2 = Arrays.stream(new String[]{"A", "B"}).parallel();

2. Stream Intermediate Operation

Stream.map
After processing the original data, new data will be generated, in which the mapToInt, mapToLong, mapToDouble methods can be directly converted to IntStream, LongStream, DoubleStream (less used, you can find it yourself)

// Original Data Add Suffix-N
List<String> result1 = Lists.newArrayList("A")
        .stream().map(item -> item + "-N").collect(Collectors.toList());
// Convert original string to array
List<String[]> result2 = Lists.newArrayList("A")
        .stream().map(item -> new String[]{item}).collect(Collectors.toList());

Stream.flatMap
Merge multiple Streams into one Stream, often used to merge multiple List data

List<String> result = Lists.newArrayList(
        Lists.newArrayList("A"),
        Lists.newArrayList("B")
).stream().flatMap(Collection::stream).collect(Collectors.toList());

Stream.filter
Element filtering, which replaces if criteria in a loop, takes a logical expression as a parameter

List<String> result = Lists.newArrayList("A", "B")
        .stream().filter("A"::equals).collect(Collectors.toList());

Stream.distinct
Element to duplicate, commonly used in simple data types, if an object can be duplicated using TreeSet Example below

// Simple data type deweighting
List<String> result1 = Lists.newArrayList("A", "A", "B")
        .stream().distinct().collect(Collectors.toList());
// Object Data Reduplication
List<Demo> result2 = Lists.newArrayList(new Demo()).stream().collect(
        Collectors.collectingAndThen(Collectors.toCollection(() ->
                new TreeSet<>(comparing(Demo::getName))), ArrayList::new)
);
@Data
class Demo {
    private String name;
    private String age;
}

Stream.peek
Processing data without changing the original data type differs from map in that peek accepts an operation with no return value and is generally used to modify the internal elements of an object

List<Demo> result = Lists.newArrayList(new Demo())
        .stream().peek(item -> item.setName("A")).collect(Collectors.toList());

Stream.sorted
Sort data, support positive and reverse order, and support object type data sorting

// Simple data type sorting
List<String> result1 = Lists.newArrayList("A", "B")
        .stream().sorted().collect(Collectors.toList());
// Object types are sorted by an attribute, in positive order by default, in reverse order using the reversed method
List<Demo> result2 = Lists.newArrayList(new Demo())
        .stream().sorted(Comparator.comparing(Demo::getName).reversed()).collect(Collectors.toList());

Stream.limit
Limit the number of final output data, intercept elements in the stream, and do not intercept by default

List<String> result1 = Lists.newArrayList("A", "B")
        .stream().limit(1).collect(Collectors.toList());

Stream.skip
How many elements are skipped before, similar to a limit, which is to intercept a stream and return it immediately when the limit is reached

List<String> result = Lists.newArrayList("A", "B")
        .stream().skip(1).collect(Collectors.toList());

3. Stream terminate operation

collect
Collectors.toList (collected as List), Collectors.joining (Collect and Stitch as String)

// Collect data as List
List<String> result1 = Lists.newArrayList("A", "B").stream().collect(Collectors.toList());
// Collection data is String, default no delimiter, delimiter can be specified using joining method with parameters
String result2 = Lists.newArrayList("A", "B").stream().collect(Collectors.joining());

reduce
The data is aggregated into a single value, and the data is converted into a single value, and a final value is calculated, which is accumulated as an example

BigDecimal result = Lists.newArrayList(BigDecimal.valueOf(1), BigDecimal.valueOf(2)).stream().reduce(BigDecimal.ZERO, BigDecimal::add);

allMatch,anyMatch,noneMatch

// All elements are greater than 1, returning true
boolean result1 = Lists.newArrayList(1, 2, 3, 4).stream().allMatch(item -> item > 1);
// Any element greater than 1 returns true
boolean result2 = Lists.newArrayList(1, 2, 3, 4).stream().anyMatch(item -> item > 1);
// No element greater than 1, returns true
boolean result3 = Lists.newArrayList(1, 2, 3, 4).stream().noneMatch(item -> item > 1);

count
Number value of Statistics

long result1 = Lists.newArrayList(1, 2, 3, 4).stream().count();

findAny,findFirst
If there is data, one is returned, the difference is that in parallel processing, findAny returns when it matches the data, and findFirst needs to wait for all data processing to complete to return the first item, so findAny is more efficient in parallel processing

// Get any one back in time
Integer result1 = Lists.newArrayList(1, 2, 3, 4).stream().findAny().get();
// All elements are executed and returned to the first
Integer result12= Lists.newArrayList(1, 2, 3, 4).parallelStream().findFirst().get();

forEach,forEachOrdered
Traversing through all elements, such as output operations, why do you need forEachOrdered with forEach? Especially in parallel execution, element execution is out of order, forEachOrdered can output results in order

// Output all elements
Lists.newArrayList(1, 2, 3, 4).stream().forEach(System.out::println);
// Sequential Output
Lists.newArrayList(1, 2, 3, 4).parallelStream().forEachOrdered(System.out::println);

max,min
Gets the maximum and minimum values of the elements in the stream. The following examples show the maximum value, and the minimum value is the same

// Simple data type
Integer result = Lists.newArrayList(1, 2, 3, 4).stream().max(Integer::compare).get();
// Compare attributes in the object to get the largest record
Demo result = Lists.newArrayList(new Demo()).stream().max(comparing(Demo::getAge)).get();

4. Stream Notes

When using parallel streams for processing, the final data must be collected, otherwise data may be lost, such as using collect or reduce, which means using collect and reduce to use parallelStream, where the entire stream processing is thread-safe

Topics: Java Lambda stream