Functional programming (Lambda && Stream)

Functional programming (Lambda & & Stream)


Reference: Java8 Actual Warfare + B Station San Geng Cao Tang

Introduced as sort

Anonymous Internal Class - >lambda

We often use the incoming Comparator to complete the incoming sorting behavior. Here we are actually passing in how to sort this behavior. lambda actually lets us focus as much as possible on the behavior itself. As for what overrides are called in the interface Comparator, we don't really need to care too much about it. Just focus on the return type.

public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 5, 2, 7, 10);
        Collections.sort(integers, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        });
        System.out.println(integers);
}

This simple example can be easily converted to lambda, which is new to IDEA's automatic conversion capabilities

 public static void main(String[] args) {
        List<Integer> integers = Arrays.asList(1, 5, 2, 7, 10);
        Collections.sort(integers, (o1, o2) -> o1-o2);
        System.out.println(integers);
 }

Several officially provided interfaces

Predicate

There are a few more commonly used interfaces that are officially provided. In fact, it doesn't matter what the name of the interface is. The specific behavior is designed by our programmers. We just need to focus on its return type, such as Predicate.

For example, if we want to make the same decision on a set of numbers, this decision returns the Boolean type, if each number is yes or no, then the test method of the interface IntPredicate provided by the government can meet our needs. We only need to call the printNum method, pass in the specific content of the decision, and then we can flexibly change the decision behavior. Java8 Actual Warfare talks a lot about the extensibility of the method. When the demand is changing, we just need to change the incoming behavior, not the entire code, which is not so elegant!

 public static void main(String[] args) {
        printNum(new IntPredicate() {
            @Override
            public boolean test(int value)
            {
                return value%2==0;

            } });
    }

    public static 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);
            } }
    }

Function

The Function function simply means passing in a type T and returning a type R. How to manipulate the incoming T is enough for you to decide your own behavior!

This greatly expands the behavior that we actually want to pass in, as it also extends to include the incoming and outgoing types that we want to be flexible and customized! For example, here we accept strings and return Integer types:

public static void main(String[] args) {

        Integer result = typeConver(new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                return Integer.valueOf(s);
            }
        });
        System.out.println(result);
    }

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

You can also change the output type, for example, to a two-tier List:

public static void main(String[] args) {

        List<List<Integer>> result = typeConver(new Function<String, List<List<Integer>>>() {
            @Override
            public List<List<Integer>> apply(String s) {

                List<Integer> integers = new ArrayList<>();
                integers.add(Integer.valueOf(s));
                List<List<Integer>> list = new ArrayList<>();
                list.add(integers);
                return list;
            }
        });
        System.out.println(result);
    }

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

Consumer

This interface returns the simplest type because it does not return, such as this IntConsumer, the accepted Int type, and nothing. Usually, we use it to print statements:

Of course there is Consumer, void accept (T) accepts generic input

 public static void main(String[] args) {
        foreachArr(new IntConsumer() {
            @Override
            public void accept(int value) {
                System.out.println(value);
            }
        });
    }

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

Supplier

Production Interface

From the parameter list and return value type of the abstract method, we know that we can create objects in the method and return the created objects

Stream Stream

Flow operations are divided into create, intermediate, and end operations:

Establish

The creation is simple, divided into collections of single-column objects, arrays, and double-column collections (Map->entrySet)

Collection:

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

Array:

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

Double column set:

Essentially, turn a Map into a single-column collection, entrySet, and do the streaming as before

Map<String,Integer> map = new HashMap<>(); 
map.put("Crayon Shin Chan",19);
map.put("sunspot",17);
map.put("Sunward Xiang Yang",16);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();

Intermediate operation

Streaming debug

filter

The filtering operation simply means that elements in a set are filtered according to each criterion, such as age greater than 18 and name length less than 3...
Predicate, where filter passes in the anonymous inner class and can make any value determination on the attributes of the element objects in the collection

 List<Author> authors = getAuthors();
        authors.stream()//Convert a set to a stream
                .distinct()//Remove duplicate authors first
                .filter(new Predicate<Author>() {
                    @Override
                    public boolean test(Author author) {
                        return  author.getAge() < 18;
                    }
                })//Screening age less than 18
                .forEach(author -> System.out.println(author.getName()));//Traversing Print Names

Simplify:

public static void main(String[] args) {
//Print the names of all writers younger than 18 and be careful to weigh them
        List<Author> authors = getAuthors();
        authors.stream()//Convert a set to a stream
                .distinct()//Remove duplicate authors first
                .filter(new Predicate<Author>() {
                    @Override
                    public boolean test(Author author) {
                        return  author.getAge() < 18;
                    }
                })//Screening age less than 18
                .forEach(author -> System.out.println(author.getName()));//Traversing Print Names
    }

map

The map operation is equivalent to mapping objects in a collection to another object one by one. For example, I want to get all the names in the author's collection, the type is List instead of List. This has already changed the element type in the collection. If you don't use streaming in the traditional way, you need to have a new List, iterate through the List, add constantly, which is quite elegant! The map operation is used here directly to get the desired mapped object.

What interface should I pass in here? It is obvious that the types are inconsistent, and what is passed in is the Function interface:

.map(new Function<Author, String>() {
  @Override
  public String apply(Author author) {
    return author.getName();
  }
})

Simplify:

   public static void main(String[] args) {

//Print the names of all writers younger than 18 and be careful to weigh them
        List<Author> authors = getAuthors();
        authors.stream()//Convert a set to a stream
                .map(author -> author.getName())
                .forEach(authorName -> System.out.println(authorName));//Traversing Print Names
    }

distinct

This is simply a de-rewrite operation, an object-based equals rewrite method, which generally defaults to comparing whether an object's attribute values are equal. If the author's name, age, and other attributes are equal, it means the same object.

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

sorted

This sorting operation, introduced at the beginning of this article, is not redundant here and goes directly to the code.

List<Author> authors = getAuthors(); The elements in the stream are sorted in descending order by age, and no duplicate elements are required.
authors.stream() 
  		.distinct()
			.sorted((o1, o2) -> o2.getAge()-o1.getAge()) 
 		 .forEach(author ->System.out.println(author.getAge()));

flatMap

FlatMap, translated as tiling, differs from map in that a map is an object mapped to a value, while an object in flatMap can correspond to multiple values, such as where each author has a collection of Books, and we want to put all Books in one collection, so we can do this:

The second parameter of Function here is Stream, remember to convert

public static void main(String[] args) {
 
   //     Print the names of all books. Removing duplicate elements is required.
        List<Author> authors = getAuthors();
        authors.stream()
                .flatMap(new Function<Author, Stream<? extends Book>>() {
                    @Override
                    public Stream<? extends Book> apply(Author author) {
                        return author.getBooks().stream();
                    }
                }) .distinct()
                .forEach(book -> System.out.println(book.getName()));
    }
public static void main(String[] args) {
 
   //     Print the names of all books. Removing duplicate elements is required.
        List<Author> authors = getAuthors();
        authors.stream()
                .flatMap(author -> author.getBooks().stream()) .distinct()
                .forEach(book -> System.out.println(book.getName()));
    }

limit

Set a return length limit.

 
List<Author> authors = getAuthors(); 
authors.stream()
.distinct()
.sorted()
.limit(2)
.forEach(author -> System.out.println(author.getName()));

skip

Skip a few

//Printing writers other than the oldest requires that no duplicate elements be present and sorted in descending order of age.
List<Author> authors = getAuthors(); 
authors.stream()
.distinct()
.sorted()
.skip(1)
.forEach(author -> System.out.println(author.getName()));

End operation

The first stream has gone through some column intermediate operations, such as filtering, weighting, mapping of the latter, and so on. Now there are these elements. To make the final output of these elements, there are several ways to traverse the foreach in the simplest way (the most common is to print out each element), count counts the number, Max&min calculates the maximum value of a value, collect is used to convert elements to List output or merge strings, and so on.

foreach

public static void main(String[] args) {

//Print the names of all writers younger than 18 and be careful to weigh them
        List<Author> authors = getAuthors();
        authors.stream()//Convert a set to a stream
                .map(author -> author.getName())
                .forEach(authorName -> System.out.println(authorName));//Traversing Print Names
 
    }

max&min

Can be used or the highest value in the stream. Example:

Get the highest and lowest scores for each of these authors'books and print them.

 
 public static void main(String[] args) {
 
        List<Author> authors = getAuthors();
        Optional<Integer> max = authors.stream()
                .flatMap(author -> author.getBooks().stream()).map(book -> book.getScore())
                .max((score1, score2) -> score1 - score2);
        Optional<Integer> min = authors.stream().flatMap(author -> author.getBooks().stream()).map(book -> book.getScore())
                .min((score1, score2) -> score1 - score2);
        System.out.println(max.get());
        System.out.println(min.get());
    }

collect

Converts the current stream into a collection. Example:

Gets a List collection that holds the names of all authors.

public static void main(String[] args) {
 
        List<Author> authors = getAuthors();
        List<String> nameList = authors.stream()
                .map(author -> author.getName())
                .collect(Collectors.toList());
        System.out.println(nameList);
    }

There is also a Sausage operation that can be converted to Map, which can be defined as key and value, type can be customized, or Function interface:

public static void main(String[] args) {
 
 List<Author> authors = getAuthors();
//        Map<String, List<Book>> map = authors.stream().distinct()
//                .collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
        Map<String, Integer> map = authors.stream().distinct()
                .collect(Collectors.toMap(author -> author.getName(), author -> author.getAge()));
        System.out.println(map);
    }

Find and Match

anyMatch

It can be used to determine if any elements match the criteria and the result is of type boolean.

Example: Determine if there are writers over 29

 
// Determine if there are writers over 29 
List<Author> authors = getAuthors(); 
boolean flag = authors.stream()
							.anyMatch(author -> author.getAge() > 29); 
System.out.println(flag);

allMatch

It can be used to determine whether all matching criteria are met, and the result is of type boolean. If the results are true, otherwise they are false. Example:

 
// Determine if all writers are adults 
List<Author> authors = getAuthors(); 
boolean flag = authors.stream()
.allMatch(author -> author.getAge() >= 18); 
System.out.println(flag);

noneMatch

You can determine if none of the elements in the stream meets the matching criteria. If none of the results are true, otherwise the result is a false example:

Determine if none of the writers are over 100 years old.

 
// Determine if none of the writers are over 100 years old. 
List<Author> authors = getAuthors();
boolean b = authors.stream()
.noneMatch(author -> author.getAge() > 100);
System.out.println(b);

findAny

Gets any element in the stream. This method has no guarantee that it must get the first element in the stream.

Example:

Get any author older than 18 and output his name if it exists

//        Gets any author older than 18 and outputs his 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.

Example:

Get the youngest author and output his name.

//        Get the youngest author and output his name.
        List<Author> authors = getAuthors();
        Optional<Author> first = authors.stream()
                .sorted((o1, o2) -> o1.getAge() - o2.getAge())
                .findFirst();

        first.ifPresent(author -> System.out.println(author.getName()));

reduce merge

Compute a result for the data in the stream according to the calculation method you specified. (Reduction)

The purpose of reduce is to combine elements in a stream, and we can pass in an initial value, which is calculated by taking the elements in the stream and the initialization values in turn, and then calculating the result with the following elements.

The internal calculation of reduce in the overloaded form of the two parameters is as follows:

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

The identity is the initial value that we can pass in through the method parameter, and what exactly accumulator's apply does is determined by the method parameter.

Example:

Use reduce to sum the ages of all authors

//        Use reduce to sum the ages of all authors
        List<Author> authors = getAuthors();
        Integer sum = authors.stream()
                .distinct()
                .map(author -> author.getAge())
                .reduce(0, (result, element) -> result + element);
        System.out.println(sum);

Use reduce to maximize age among all authors

//        Use reduce to maximize age among all authors
        List<Author> authors = getAuthors();
        Integer max = authors.stream()
                .map(author -> author.getAge())
                .reduce(Integer.MIN_VALUE, (result, element) -> result < element ? element : result);

        System.out.println(max);

Minimum Age of All Authors Using reduce

//        Minimum Age of All Authors Using reduce
        List<Author> authors = getAuthors();
        Integer min = authors.stream()
                .map(author -> author.getAge())
                .reduce(Integer.MAX_VALUE, (result, element) -> result > element ? element : result);
        System.out.println(min);

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 a parameter's overload method is used to find the minimum code is as follows:

        //        Minimum Age of All Authors Using reduce
        List<Author> authors = getAuthors();
        Optional<Integer> minOptional = authors.stream()
                .map(author -> author.getAge())
                .reduce((result, element) -> result > element ? element : result);
        minOptional.ifPresent(age-> System.out.println(age));

Matters needing attention

  • Lazy evaluation (without an end operation, no intermediate operation will be executed)
  • Stream is one-time (once a stream object has undergone a finalization operation. The stream can no longer be used)
  • It doesn't affect the original data (we can do a lot of processing with more data in the stream. But normally it doesn't affect the elements in the original set. This is often what we expect)

Optional

The most common null pointer exception we encounter when writing code is a 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 greater.

Too many sentences can make our code look bloated.

Optional was introduced in JDK8, so you can write more elegant code to avoid null pointer exceptions when you get used to using Optional.

Optional is also used in many API s related to functional programming, and learning about functional programming can be affected if Optional is not used.

Use

create object

Optional is like a wrapper class that encapsulates our specific data inside an Optional object. Then we can use the encapsulated method in Optional to manipulate the encapsulated data in order to avoid null pointer exceptions gracefully.

We usually encapsulate data as an Optional object using Optional's static method ofNullable. No matter whether the parameter passed in is null or not, there will be no problem.

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

You may find it cumbersome to add another line of code to encapsulate the data. But if the getAuthor method is modified so that its return value is the encapsulated Optional, it will be much easier to use.

And in practice, much of our data is derived from databases. Mybatis can and can already support Optional from version 3.5. We can directly define the return value type of the dao method as Optional, and MyBastis will encapsulate the data itself as Optional object returns. The packaging process does not require our own operation.

If you are sure that an object is not empty, you can use Optional's static method of of of to encapsulate data as Optional objects.

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

However, it is important to note that the parameter passed in when using of must not be null. (What happens when you try to download an incoming null)

If the return value type of a method is Optional. If we decide that a calculated return value is null, then we need to encapsulate null as an Optional object to return. Optional's static method, empty, can be used for encapsulation.

		Optional.empty()

‚Äč

So in the end, which method would be more convenient? ofNullable

Safe consumption value

Once we get an Optional object, we definitely need to use the data. At this point we can use its ifPresent method to consume its value.

This method will determine if the encapsulated data is empty and will execute the specific consumption code when it is not empty. This makes it safer to use.

For example, the following gracefully avoids null pointer exceptions.

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

        authorOptional.ifPresent(author -> System.out.println(author.getName()));

Get value

The get method can be used if we want to get the value for our own processing, but it is not recommended. Because exceptions occur when the data inside Optional is empty.

Secure Get Value

If we expect a safe value to be obtained. We do not recommend using the get method, but use 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, you can get it. If null, create an object based on the parameters you passed in and return it as the default value.

            Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
            Author author1 = authorOptional.orElseGet(() -> new Author());
    
  • orElseThrow

    Get the data, and you can get it if it's not empty. If null, create an exception throw based on the parameters you passed in.

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

filter

We can use the filter method to filter the data. If there is data but it doesn't fit your judgment, it will also become an Optional object with no data.

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

judge

We can use the isPresent method to determine whether there is data or not. If null returns false, if not null returns true. However, this does not reflect the benefits of Optional, and the ifPresent method is recommended.

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

        if (authorOptional.isPresent()) {
            System.out.println(authorOptional.get().getName());
        }

data conversion

Optional also provides a map that allows us to convert data, and the converted data is still packaged by Optional, which ensures our safety.

For example, we want to get a collection of books by authors.

    private static void testMap() {
        Optional<Author> authorOptional = getAuthorOptional();
        Optional<List<Book>> optionalBooks = authorOptional.map(author -> author.getBooks());
        optionalBooks.ifPresent(books -> System.out.println(books));
    }

Tags: Java Big Data socket TCPIP

Posted on Sat, 27 Nov 2021 12:44:11 -0500 by BlackKite