Functional programming

Posted by ngubie on Mon, 24 Jan 2022 11:14:31 +0100

1, Functional programming idea

1.1 concept

Object oriented thinking needs to focus on what objects are used to accomplish what. The idea of functional programming is similar to the function in our mathematics. It mainly focuses on what to do with the data.

Functional programming only focuses on the specific method body and parameter list, and nothing else

1.2 advantages

  • Simple code and fast development
  • Close to natural language and easy to understand
  • Easy "concurrent programming"

2, Lambda expression

2.1 overview

Lambda yes)DK8 A grammar sugar in. It can be regarded as a syntax sugar, which can simplify the writing of some anonymous inner classes. It is an important embodiment of the idea of functional programming. Let's not focus on what it is. It's more about what we do with the data.

2.2 core principles

Derivable and omitted

2.3 basic format

()->{code}

Example 1

We can write anonymous inner classes when creating threads and starting them

new Thread(new Runable(){
    @Override
    public void run() {
        System.out.println("HuDu");
    }
}).start();

You can modify it using Lambda's format. Revised as follows:

new Thread(()->{
    System.out.println("HuDu");
}).start();

Example 2

The existing methods are defined as follows, where IntBinaryOperator is an interface. First call this method with the writing method of anonymous inner class. The parameter name of anonymous inner class can be modified at will.

public int calculateNum(IntBinaryOperator operator) {
    int a = 10;
    int b = 20;
    return operator.applyAsInt(a,b);
}

public void lambdaTest_2_1() {
    System.out.println(calculateNum(new IntBinaryOperator() {
        @Override
        public int applyAsInt(int left, int right) {
            return left + right;
        }
    }));
}

Lambda:

public void lambdaTest_2_3() {
    System.out.println(calculateNum(Integer::sum));
}

Example 3

The existing methods are defined as follows, where intpredict is an interface. The method is called first using the writing method of the anonymous inner class.

public void printNum(IntPredicate predicate) {
    int[] arr = {1,2,3,4,5,6,7,8,9,10};
    for (int i : arr) {
        if (predicate.test(i)) {
            System.out.println(i);
        }
    }
}

@Test
public void lambdaTest_3_1() {
  printNum(new IntPredicate() {
  @Override
  public boolean test(int value) {
      return value %2 == 0;
  }
 });
}

Lambda writing

public void lambdaTest_3_3() {
    printNum((int value) -> 0 == value % 2);
}

Example 4

The existing methods are defined as follows, where Function is an interface. First call this method using the writing method of the anonymous inner class.

public <R> R typeConvert(Function<String,R> function) {
    String str = "1235";
    R result = function.apply(str);
    return result;
}

@Test
public void lambdaTest_4_1() {
    System.out.println(typeConvert(new Function<String, Integer>() {
        @Override
        public Integer apply(String s) {
            return Integer.valueOf(s);
        }
    }));
}

Lambda calling method

System.out.println(typeConvert((Function<String, Integer>) Integer::valueOf));

Example 5

The existing methods are defined as follows, where IntConsumer is an interface. First call this method using the writing method of the anonymous inner class.

public void foreachArr(IntConsumer consumer) {
        int[] arr = {1,2,3,4,5,6,7,8,9,10};
        for (int i : arr) {
            consumer.accept(i);
        }
    }

Anonymous inner class usage

@Test
public void lambdaTest_5_1() {
  foreachArr(new IntConsumer() {
  @Override
  public void accept(int value) {
  System.out.println(value);
  }
 });
}

lambda usage

Method reference
foreachArr(System.out::println);

2.4 omission rules

  • Parameter types can be omitted
  • When the method body has only one sentence of code, the braces return and the semicolon of the only sentence of code can be omitted
  • Parentheses can be omitted when the method has only one parameter

3, Stream stream

3.1 overview

Java8 of Stream It uses the functional programming pattern, just like its name, It can be used to perform chain flow operations on sets or arrays. It is more convenient for us to operate on sets or arrays.

3.2 case data preparation

@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode // Used for later de duplication
public class Author {
    private Long id;
    private String name;
    private Integer age;
    // brief introduction
    private String intro;
    private List<Book> books;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode
public class Book {
    private Long id;
    private String name;
    private String category;
    private Integer score;
    private String intro;
}
public List<Author> getAuthors() {
        // Data initialization
        Author author1 = new Author(1L, "Alice", 33, "test", null);
        Author author2 = new Author(2L, "Mike", 15, "test", null);
        Author author3 = new Author(3L, "Lucy", 14, "test", null);
        Author author4 = new Author(4L, "Lili", 14, "test", null);

        // Book list

        List<Book> books1 = new ArrayList<>();
        List<Book> books2 = new ArrayList<>();
        List<Book> books3 = new ArrayList<>();

        books1.add(new Book(1L,"book1","category1,category2",88,"book1 intro"));
        books1.add(new Book(2L,"book2","category3,category2",99,"book2 intro"));

        books2.add(new Book(3L,"book3","category1",85,"book3 intro"));
        books2.add(new Book(3L,"book3","category1",85,"book3 intro"));
        books2.add(new Book(4L,"book4","category2,category4",56,"book4 intro"));

        books3.add(new Book(5L,"book5","category2",56,"book5 intro"));
        books3.add(new Book(6L,"book6","category4",100,"book6 intro"));
        books3.add(new Book(6L,"book6","category4",100,"book6 intro"));

        author1.setBooks(books1);
        author2.setBooks(books2);
        author3.setBooks(books3);
        author4.setBooks(books3);

        ArrayList<Author> authors = new ArrayList<>(Arrays.asList(author1, author2, author3, author4));
        return authors;

    }

3.3. Quick start

3.3.1 requirements

We can call the getAuthors method to get the collection of writers. Now you need to print the names of all writers younger than 18, and pay attention to repetition.

3.3.2 realization

// Print the names of all writers younger than 18 and pay attention to de duplication
        List<Author> authors = getAuthors();
        authors.
            stream() //Convert a collection into a stream
            .distinct() //Remove duplicate writers first
            .filter(author -> author.getAge() < 18) //Screening younger than 18
            .forEach(author -> System.out.println(author.getName())); //Traversal print age less than 18

3.4 common operations

3.4.1. Create flow

Single column Collections: collection objects stream()

List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();

Arrays: arrays Stream (array) or use stream Of to create

Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
// Static method call using Stream interface
Stream<Integer> stream2 = Stream.of(arr);

Double column set: convert to single column before creating

Map<String,Integer> map = new HashMap<>();
map.put("Crayon Shin Chan",8);
map.put("sunspot",12);
map.put("HuDu",198);

Stream<Map.Entry<String,Integer>> stream = map.entrySet.stream();

3.4.2 intermediate operation

filter

You can conditionally filter the elements in the stream. Only those that meet the filtering conditions can continue to stay in the stream.

// Print the names of all writers whose names are longer than 4
List<Author> authors = getAuthors();
authors.stream()
  .filter(author -> author.getName().length() > 4)
  .forEach(author -> System.out.println(author.getName()));
map

The elements in the convection can be calculated or converted.

// Print the names of all writers
List<Author> authors = getAuthors();
authors.stream()
  .map(author -> author.getName())
  .forEach(name -> System.out.println(name));
distinct

You can remove duplicate elements from the stream.

// Print the names of all writers, and there must be no duplicate elements
List<Author> authors = getAuthors();
authors.stream()
  .distinct()
  .map(author -> author.getName())
  .forEach(name -> System.out.println(name));

Note: the distinct method depends on the equals method of the Object to determine whether it is the same Object. Therefore, you need to pay attention to the equals method again.

sorted

You can sort elements in a stream.

// The elements in the convection are sorted in descending order by age, and there must be no duplicate elements
List<Author> authors = getAuthors();
authors.stream()
  .distinct()
  .sorted()
  .map(author -> author.getName())
  .forEach(name -> System.out.println(name));

List<Author> authors = getAuthors();
  authors.stream()
  .distinct()
//                .sorted()
  .sorted((o1, o2) ->o1.getAge() - o2.getAge())
  .map(author -> author.getName())
  .forEach(name -> System.out.println(name));

Note: if the sorted() method of the null parameter is called, the element in the stream needs to implement Comparable.

@Data
@NoArgsConstructor
@AllArgsConstructor
// Override the equals and hashcode methods
@EqualsAndHashCode // Used for later de duplication
public class Author implements Comparable<Author>{
    private Long id;
    private String name;
    private Integer age;
    // brief introduction
    private String intro;
    private List<Book> books;

    /**
     * Compare itself to the incoming object
     * @param o Incoming object
     */
    @Override
    public int compareTo(Author o) {
        return this.getAge() - o.getAge();
    }
}
limit

The maximum length of the stream can be set, and the excess will be discarded.

// The elements in the stream are sorted in descending order according to age, and it is required that there should be no duplicate elements, and then the names of the two oldest writers are printed.
List<Author> authors = getAuthors();
authors.stream()
  .distinct()
  .sorted((o1, o2) ->o1.getAge() - o2.getAge())
  .map(author -> author.getName())
  .limit(2)
  .forEach(name -> System.out.println(name));
skip

Skip the first few elements in the stream

//Print writers other than the oldest writers. It is required that there should be no duplicate complex elements and they should be sorted in descending order of age.
authors.stream()
  .distinct()
  .sorted((o1, o2) ->o1.getAge() - o2.getAge())
  .map(author -> author.getName())
  .skip(1)
  .forEach(name -> System.out.println(name));
flatMap

A map can only convert one object to another as an element in a stream. flatMap can convert an object into multiple objects as elements in the stream.

Example 1

// Print the names of all books and remove duplicate data
List<Author> authors = getAuthors();
authors.stream()
  .flatMap(new Function<Author, Stream<Book>>() {
  @Override
  public Stream<Book> apply(Author author) {
  return author.getBooks().stream();
  }
 })  .distinct()
  .forEach(new Consumer<Book>() {
  @Override
  public void accept(Book book) {
  System.out.println(book.getName());
  }
 });


List<Author> authors = getAuthors();
authors.stream()
  .flatMap(author -> author.getBooks().stream())
  .distinct()
  .forEach(book -> System.out.println(book.getName()));

Example 2

// When printing existing book types, formats like category1 and category2 cannot appear
List<Author> authors = getAuthors();
authors.stream()
  .flatMap(author -> author.getBooks().stream())
  .distinct()
  .flatMap(book -> Arrays.stream(book.getCategory().split(",")))
  .distinct()
  .forEach(category-> System.out.println(category));

3.4.3 termination operation

foreach

The traversal operation is performed on the meta cable in the stream. We specify what specific operation is performed on the traversed meta cable through the passed parameters.

// Output all writer names
List<Author> authors = getAuthors();
authors.stream()
  .map(author -> author.getName())
  .distinct()
  .forEach(name -> System.out.println(name));
count

It can be used to obtain the number of elements in the current money flow.

// Print the number of books published by these writers and go back
long count = authors.stream()
  .flatMap(author -> author.getBooks().stream())
  .distinct()
  .count();
max&min

It can be used to get the latest value in the stream

// Obtain the highest and lowest scores of the books produced by these writers respectively and print them.
List<Author> authors = getAuthors();
Optional<Integer> max = authors.stream()
  .flatMap(author -> author.getBooks().stream())
  .map(book -> book.getScore())
  .max((o1, o2) -> o1 - o2);
System.out.println(max.get());

List<Author> authors = getAuthors();
Optional<Integer> min = authors.stream()
  .flatMap(author -> author.getBooks().stream())
  .map(book -> book.getScore())
  .min((o1,o2)->o1-o2);
System.out.println(min.get());
collect

Converts the current stream to a collection

// Gets a List collection that holds the names of all authors
List<Author> authors = getAuthors();
List<String> nameList = authors.stream()
  .map(author -> author.getName())
  .distinct()
  .collect(Collectors.toList());
// Gets a Set collection of all book titles
List<Author> authors = getAuthors();
Set<Book> bookSet = authors.stream()
  .flatMap(author -> author.getBooks().stream())
  .collect(Collectors.toSet());
// Get a map set, with the key of the map as the author name and the value as list < book >

// Anonymous inner class writing
List<Author> authors = getAuthors();
Map<String, List<Book>> map = authors.stream()
  .distinct()
  .collect(Collectors.toMap(new Function<Author, String>() {
  @Override
  public String apply(Author author) {
  return author.getName();
  }
 }, new Function<Author, List<Book>>() {
  @Override
  public List<Book> apply(Author author) {
  return author.getBooks();
  }
 }));

// Slightly simplified expression
List<Author> authors = getAuthors();
Map<String, List<Book>> map = authors.stream()
  .distinct()
  .collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
Find and match
anyMatch

It can be used to judge whether there are any qualified elements, and the result is boolean type

// Judge whether there are writers over the age of 29
List<Author> authors = getAuthors();
boolean flag = authors.stream()
  .anyMatch(new Predicate<Author>() {
  @Override
  public boolean test(Author author) {
  return author.getAge() > 29;
  }
 });

reduce to
List<Author> authors = getAuthors();
boolean flag = authors.stream()
  .anyMatch(author -> author.getAge() > 29);
allMatch

It can be used to judge whether they all meet the matching conditions. The result is of boolean type. If both match, the result is true, otherwise the result is false.

// Judge whether all writers are adults
List<Author> authors = getAuthors();
boolean flag = authors.stream()
  .allMatch(author -> author.getAge() >= 18);
noneMatch

You can judge whether the elements in the flow do not meet the matching conditions, and the results do not meet the matching conditions. The result is true, otherwise it is false

// Judge whether no writer is over 100 years old.
boolean flag = authors.stream()
  .noneMatch(author -> author.getAge() > 100);
findAny

Gets any element in the stream. This method has no way to ensure that the first element in the stream is obtained.

// Get any writer greater than 18, and print the name if it exists
List<Author> authors = getAuthors();
Optional<Author> optionalAuthor = authors.stream()
  .filter(author -> author.getAge() > 18)
  .findAny();
optionalAuthor.ifPresent(author -> System.out.println(author.getName()));
findFirst

Gets the first element in the stream

// Get the youngest writer and print the name
List<Author> authors = getAuthors();
Optional<Author> optionalAuthor = authors.stream()
  .sorted((age1,age2)->age1.getAge()-age2.getAge())
  .findFirst();
optionalAuthor.ifPresent(author -> System.out.println(author.getName()));
reduce merge

The data in the convection calculates a result according to the calculation method you specify. (reduction operation)
The function of reduce is to combine the elements in the stream. We can pass in an initial value. It will calculate the elements and initialization values in the stream in turn according to our calculation method, and then calculate the calculation results with the following elements
The internal classes of the overload mode of the reduce two parameters are as follows:

T result = idnetity;
for (T element : this stream)
    result = accumulator.apply(result, element)
return result;

Among them, identity is the initial value that we can pass in through the method parameters. The application of the calculator is explained in detail, and the calculation is also determined through the method parameters.

example:

// Use reduce to sum the ages of all authors
List<Author> authors = getAuthors();
Integer sum = authors.stream()
  .distinct()
  .map(author -> author.getAge())
  .reduce(0, new BinaryOperator<Integer>() {
  @Override
  public Integer apply(Integer result, Integer element) {
  return result + element;
  }
 });

The simplified form is as follows
List<Author> authors = getAuthors();
Integer sum = authors.stream()
  .distinct()
  .map(author -> author.getAge())
  .reduce(0, (result, element) -> result + element);
// Use reduce to find the maximum age of all authors
List<Author> authors = getAuthors();
Integer max = authors.stream()
  .map(author -> author.getAge())
  .reduce(Integer.MIN_VALUE, new BinaryOperator<Integer>() {
  @Override
  public Integer apply(Integer result, Integer element) {
  return result < element ? element : result;
  }
 });
// Use reduce to find the minimum age of all authors
List<Author> authors = getAuthors();
Integer min = authors.stream()
  .map(author -> author.getAge())
  .reduce(Integer.MAX_VALUE, (result, element) -> result > element ? element : result);

reduce the internal calculation of an overloaded form of a parameter

boolean foundAny = false;
     T result = null;
     for (T element : this stream) {
         if (!foundAny) {
             foundAny = true;
             result = element;
         }
         else
             result = accumulator.apply(result, element);
     }
     return foundAny ? Optional.of(result) : Optional.empty();

If the minimum value is obtained in the overloaded form of a parameter, the code is as follows

List<Author> authors = getAuthors();
        Optional<Integer> minOptional = authors.stream()
                .map(author -> author.getAge())
                .reduce(new BinaryOperator<Integer>() {
                    @Override
                    public Integer apply(Integer result, Integer element) {
                        return result > element ? element : result;
                    }
                });
minOptional.ifPresent(age -> System.out.println(age));

3.5 precautions

  • Lazy evaluation (if there is no termination operation, no intermediate operation will not be executed)
  • Stream one-time (once a stream object passes through a termination operation, the stream can no longer be used)
  • It will not affect the original data (we can do a lot of processing with more data in the stream. But under normal circumstances, it will not affect the elements in the original set. This is often what we expect.)

4, Optional

4.1 overview

When we write code, the most common exception is null pointer exception. So in many cases, we need to make all kinds of non empty judgments.

For example:

Author author = getAuthor();
if(author != null) {
  System.out.println(author.getName());
}

Especially if the attribute in the object is still an object. This judgment will be more.
Too many judgment statements will make our code look bloated.
Therefore, Optional is introduced into JDK8. After you get into the habit of using Optional, you can write more elegant code to avoid null pointer exceptions.
Moreover, Optional is also used in many API s related to functional programming. If you don't use Optional, it will also affect the learning of functional programming.

4.2 use

4.2.1 creating objects

Optional is like a wrapper class, which can encapsulate our specific data inside the optional object. Then we use the encapsulated method in optional to operate the encapsulated number
It can be very elegant to avoid null pointer exceptions.

We generally use the Optional static method ofNullable to encapsulate the data into an Optional object. There is no problem whether the passed in parameter is null or not.

Author author = getAuthor();
Optional<Author> authorOptional = Optional.ofNullable(author);

You may find it troublesome to add a line of code to encapsulate the data. However, if the getAuthor method is modified so that its return value is encapsulated Optional, it will be much more convenient for us to use.
And in the actual development, many of our data are obtained from the database. Mybatis can and already supports Optional from version 3.5. We can return the dao method directly
The value type is defined as an Optional type, and MyBastis will encapsulate the data into an Optional object and return it. The packaging process does not need our own operation.

If you are sure that an object is not empty, you can use the Optional static method of to encapsulate the data into an Optional object

Author author = new Author();
Optional<Author> authorOptional = Optional.of(author);

However, it must be noted that if of is used, the parameters passed in must not be null. (what happens when you try to pass in null)

If the return value type of a method is Optional. If we find that the return value of a calculation is null after judgment, we need to encapsulate null into an Optional object for return. In this case, you can use the Optional static method empty to encapsulate.

Optional.empty()

4.2.2 safe consumption

After we get an Optional object, we must use the data in it. At this time, we can use its ifPresent method pair to consume the value.
This method will judge whether the encapsulated data is empty. When it is not empty, the specific consumption code will be executed. This makes it safer to use.
For example, the following wording gracefully avoids null pointer exceptions.

Optional<Author> optionalAuthor = Optional.ofNullable(getAuthor());
        optionalAuthor.ifPresent(author1 -> System.out.println(author1.getName()));

4.2.3. Get value

If we want to get the value and process it ourselves, we can use the ge method, but it is not recommended. Because an exception will occur when the internal data of option is empty.

4.2.4. Safety acquisition value

If we expect to get the value safely. Instead of using the get method, we recommend using the following methods provided by Optional.

  • orElseGet
    Gets the data and sets the default value when the data is empty. If the data is not empty, the data can be obtained. If it is empty, the object will be created according to the parameters you pass in and returned as the default value.
Optional<Author> authorOptional = getAuthorOptional();
Author author = authorOptional.orElseGet(() -> new Author());
  • orElseThrow
    Get the data. If the data is not empty, you can get the data. If it is empty, an exception throw will be created according to the parameters you pass in.
Optional<Author> optionalAuthor = Optional.ofNullable(getAuthor());
        try {
            Author author = optionalAuthor.orElseThrow(new Supplier<Throwable>() {
                @Override
                public Throwable get() {
                    return new RuntimeException("author Empty");
                }
            });
            System.out.println(author.getName());
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }

// Simplify as follows

Optional<Author> optionalAuthor = Optional.ofNullable(getAuthor());
try {
  Author author = optionalAuthor.orElseThrow(() -> new RuntimeException("author Empty"));
  System.out.println(author.getName());
} catch (Throwable throwable) {
 throwable.printStackTrace();
}

4.2.5 filtering

We can use the filter method to filter the data. If there is data originally, but it does not meet the judgment, it will also become an Optional object without data.

Optional<Author> optionalAuthor = Optional.ofNullable(getAuthor());
Optional<Author> authorOptional = optionalAuthor.filter(author -> author.getAge() > 18);
authorOptional.ifPresent(author -> System.out.println(author.getName()));

4.2.6 judgment

We can use isPresent method to judge whether there is data. If it is empty, the return value is false. If it is not empty, the return value is true. However, this method does not reflect the benefits of Optional, and ifPresent method is recommended.

Optional<Author> optionalAuthor = Optional.ofNullable(getAuthor());
if (optionalAuthor.isPresent()) {
  System.out.println(optionalAuthor.get().getName());
}

4.2.7 data conversion

Optional also provides a map that allows us to convert the data, and the converted data is packaged by optional to ensure our safety.
For example, we want to get a collection of writers' books.

Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
        Optional<List<Book>> books = authorOptional.map(author -> author.getBooks());
        books.ifPresent(new Consumer<List<Book>>() {
            @Override
            public void accept(List<Book> books) {
                books.forEach(book -> System.out.println(book.getName()));
            }
        });

5, Functional interface

5.1 overview

An interface with only one abstract method is called a function interface
The functional interfaces of JDK are marked with @ functional interface annotation. However, whether the annotation is added or not, as long as there is only one abstract method in the interface, it is a functional interface.

5.2 common functional interfaces

  • Consumer consumer interface
    According to the parameter list and return value type of the abstract method, we can consume the incoming parameters in the method. Commonly used in forEach() medium

  • Function calculation conversion interface
    According to the parameter list and return value type of the abstract method, we can calculate or convert the incoming parameters in the method and return the results. Common in map()

  • Predict judgment interface
    According to the parameter list and return value type of the abstract method, we can judge the passed parameter conditions in the method and return the judgment results. Common in filter()

  • Supplier production interface
    According to the parameter list and return value type of the abstract method, we can create objects in the method and return the created objects.

5.3 common default methods

  • and
    We may need to splice judgment conditions when we use Predicate. The and method is equivalent to using & & to splice two judgment conditions. Application scenarios are generally used when we define methods ourselves
// Print writers older than 17 and whose name length is greater than 1.
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.filter(new Predicate<Author>() {
  @Override
  public boolean test(Author author) {
  return author.getAge() > 17;
  }
}.and(new Predicate<Author>() {
  @Override
  public boolean test(Author author) {
  return author.getName().length() > 3;
  }
})).forEach(author -> System.out.println(author));

Simplify as follows

List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.filter(((Predicate<Author>) author -> author.getAge() > 17).and(author -> author.getName().length() > 3)).forEach(author -> System.out.println(author));

Custom method printNumber

public void functionTest_1_3() {
        printNumber(new IntPredicate() {
            @Override
            public boolean test(int value) {
                return value % 2 == 0;
            }
        }, new IntPredicate() {
            @Override
            public boolean test(int value) {
                return value > 4;
            }
        });
    }

    public void printNumber(IntPredicate predicate1,IntPredicate predicate2) {
        int[] arr = {1,2,3,4,5,6,7,8,9,10};
        for (int i : arr) {
            if (predicate1.and(predicate2).test(i)) {
                System.out.println(i);
            }
        }
    }

// Simplify as follows
printNumber(value -> value % 2 == 0, value -> value > 4);
  • or
    We may need to splice judgment conditions when using the predict interface. The or method is equivalent to using | to splice two judgment conditions.

    // Print writers who are older than 17 or whose names are less than 2 in length.
    List<Author> authors = getAuthors();
    Stream<Author> authorStream = authors.stream();
    authorStream.filter(((Predicate<Author>) author -> author.getAge() > 17).or(author -> author.getName().length() < 2)).forEach(author -> System.out.println(author));
  • negate

Methods in the predict interface. The negate method is equivalent to adding a before the judgment! Indicates negation

// Print writers who are not older than 17
List<Author> authors = getAuthors();
        Stream<Author> authorStream = authors.stream();
        authorStream.filter(new Predicate<Author>() {
            @Override
            public boolean test(Author author) {
                return !(author.getAge() > 17);
            }
        }).forEach(author -> System.out.println(author.getAge()));

// Use negate
authorStream.filter(((Predicate<Author>) author -> author.getAge() > 17).negate()).forEach(author -> System.out.println(author.getAge()));

6, Method reference

When we use lambda, if there are only - method calls in the method body (including construction methods), we can further simplify the code with method references.

6.1 recommended usage

When using lambda, we don't need to consider when to use method reference, which method to use, and what format the method | uses. After writing the lambda method, we only need to use the shortcut key to try whether it can be converted into a method reference when we find that the method body has only one line of code and is a method call.
When we use more method references, we can write method references directly slowly.

6.2 basic format

Class name or object name:: method name

6.3 grammar explanation (understanding)

6.3.1 reference static method

It's actually a static method that references a class

format

Class name::Method name

Use premise

If there is only one line of code in the method body when we rewrite the method, and this line of code calls the static method of a class, and we pass all the parameters in the abstract method to be rewritten into the static method in order, then we can reference the static method of the class.

For example:

After optimization

List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream
  .map(author -> author.getAge())
  .map(String::valueOf)
  .forEach(age -> System.out.println(age));

6.3.2 entity class method of reference object

format

Object name::Method name

Use premise
If there is only one line of code in the method body when we rewrite the method, and this line of code calls the member method of an object, and we pass all the parameters in the abstract method to be rewritten into the member method in order, then we can reference the instance method of the object

for example

After optimization

List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
StringBuilder sb = new StringBuilder();
authorStream
  .map(author -> author.getName())
  .forEach(sb::append);

6.3.3 instance method of reference object

format

Class name::Method name

Use premise
If there is only one line of code in the method body when we rewrite the method, and this line of code is the member method that calls the first parameter, and we pass all the remaining parameters in the abstract method to be rewritten into the member method in order, then we can reference the instance method of the class.

for example

public void MethodReferenceTest_3_1() {
        subAuthorName("radian", new UseString() {
            @Override
            public String use(String str, int start, int length) {
                return str.substring(start,length);
            }
        });
    }

After optimization

public void MethodReferenceTest_3_2() {
        subAuthorName("radian", String::substring);
    }

6.3.4 constructor reference

If a line of code in the method body is a constructor, you can use the constructor reference

format

Class name::new

Use premise
If there is only one line of code in the method body when we rewrite the method, and this line of code calls the constructor of a class, and we pass all the parameters in the abstract method to be rewritten into the constructor in order, then we can reference the constructor.

For example:

List<Author> authors = getAuthors();
authors.stream()
  .map(author -> author.getName())
  .map(name -> new StringBuilder(name))
  .map(sb -> sb.append("hudu").toString())
  .forEach(str -> System.out.println(str));

After simplification

List<Author> authors = getAuthors();
authors.stream()
  .map(author -> author.getName())
  .map(StringBuilder::new)
  .map(sb -> sb.append("hudu").toString())
  .forEach(System.out::println);

7. Advanced usage

7.1. Basic data type optimization

Many of the Stream methods we used earlier use generics. Therefore, the parameters and return values involved are reference data types.
Even if we operate on integers and decimals, we actually use their wrapper classes. The automatic packing and unpacking introduced in JDK5 make it as convenient as using basic data types when using the corresponding packing classes. But you must know that packing and unpacking must take time. Although this time consumption is very small. But when a large amount of data is repeatedly packed and unpacked, you can't ignore this time loss.
So in order to optimize the time consumption of this part. Stream also provides many methods specifically for basic data types.

For example: mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToDouble, etc.

List<Author> authors = getAuthors();
authors.stream()
  .map(author -> author.getAge())
  .map(age -> age + 10)
  .filter(age -> age > 18)
  .map(age -> age + 2)
  .forEach(System.out::println);

After optimization

List<Author> authors = getAuthors();
authors.stream()
  .mapToInt(Author::getAge)
  .map(age -> age + 10)
  .filter(age -> age > 18)
   .map(age -> age + 2)
  .forEach(System.out::println);

7.2 parallel flow

When there are a large number of elements in the flow, we can use parallel flow to improve the efficiency of operation. Real parallel flow is to assign tasks to multiple threads to complete. If we implement it in code, it will be very complex, and you are required to have sufficient understanding and understanding of concurrent programming. If we use Stream, we only need to modify the call of a method, and we can use parallel flow to help us implement it, so as to improve efficiency.

for example

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
stream
  .filter(num -> num > 5)
  .reduce(Integer::sum)
  .ifPresent(System.out::println);

After optimization

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
stream.parallel()
  .filter(num -> num > 5)
  .reduce(Integer::sum)
  .ifPresent(System.out::println);

The parallel method can convert a serial stream into a parallel stream, or you can directly use the parallel stream method. You can debug breakpoints through peek() to see which thread is executing.

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
stream.parallel()
  .peek(new Consumer<Integer>() {
    @Override
    public void accept(Integer num) {
    System.out.println(num+":"+Thread.currentThread().getName());
    }
  })
  .filter(num -> num > 5)
  .reduce(Integer::sum)
  .ifPresent(System.out::println);

Topics: server